├── .babelrc ├── .bowerrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── app.config.js ├── app.js ├── common │ ├── config.js │ ├── exception.js │ ├── index.js │ └── logger.js ├── components │ ├── index.js │ └── message-list │ │ ├── message-list.directive.html │ │ └── message-list.directive.js ├── filters │ ├── index.js │ └── whitespace.filter.js ├── interceptors │ └── seo-interceptor.js ├── layout │ ├── app.css │ ├── layout.controller.js │ └── styles.js ├── messages │ ├── index.js │ ├── list.html │ ├── messages.config.js │ └── messages.controller.js ├── services │ ├── index.js │ └── message.service.js └── templates.js ├── bower.json ├── e2e-tests └── protractor.conf.js ├── gulpfile.js ├── index.js ├── jsconfig.json ├── karma.conf.js ├── package.json ├── public ├── db.json └── index.html ├── tests ├── common │ ├── exception.spec.js │ └── logger.spec.js ├── components │ └── message-list.directive.spec.js ├── filters │ └── whitespace.filter.spec.js ├── interceptors │ └── seo-interceptor.spec.js ├── layout │ └── layout-controller.spec.js ├── messages │ └── messages-controller.spec.js ├── services │ └── message.service.spec.js └── tests.webpack.js ├── webpack.build.js ├── webpack.config.js ├── webpack.make.js └── webpack.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "json": "bower.json" 4 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.html 2 | **/templates.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "arrowFunctions": true, 4 | "blockBindings": true, 5 | "classes": true, 6 | "defaultParams": true, 7 | "destructuring": true, 8 | "forOf": true, 9 | "generators": false, 10 | "modules": true, 11 | "objectLiteralComputedProperties": true, 12 | "objectLiteralDuplicateProperties": false, 13 | "objectLiteralShorthandMethods": true, 14 | "objectLiteralShorthandProperties": true, 15 | "restParams": true, 16 | "spread": true, 17 | "superInFunctions": true, 18 | "templateStrings": true, 19 | "jsx": false 20 | }, 21 | "env": { 22 | "browser": true, 23 | "node": true, 24 | "es6": true 25 | }, 26 | "rules": { 27 | "strict": 0, 28 | "no-underscore-dangle": 0, 29 | "quotes": [ 30 | 2, 31 | "single" 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | .idea/ 17 | node_modules/ 18 | coverage/ 19 | bower_components/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.1.0" 4 | matrix: 5 | allow_failures: 6 | - node_js: "5.1.0" 7 | fast_finish: true 8 | before_install: 9 | - "npm install -g gulp" 10 | - "npm install -g bower" 11 | before_script: 12 | - "npm install" 13 | - "bower install" 14 | - "npm install coveralls" 15 | script: 16 | - "npm run-script test-travis" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ziya SARIKAYA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angularjs ES6 Webpack boilerplate 2 | Angular 1.5.9 + ES6 application boilerplate with testing practices 3 | [![Dependency Status](https://david-dm.org/ziyasal/ng-espack-boilerplate.svg)](https://david-dm.org/ziyasal/ng-espack-boilerplate) [![devDependency Status](https://david-dm.org/ziyasal/ng-espack-boilerplate/dev-status.svg)](https://david-dm.org/ziyasal/ng-espack-boilerplate#info=devDependencies) [![Build Status](https://travis-ci.org/ziyasal/ng-espack-boilerplate.svg)](https://travis-ci.org/ziyasal/ng-espack-boilerplate) [![Coverage Status](https://coveralls.io/repos/ziyasal/ng-espack-boilerplate/badge.svg?branch=master&service=github)](https://coveralls.io/github/ziyasal/ng-espack-boilerplate?branch=master) 4 | 5 | >_Inspired from [angular-webpack-workflow](https://github.com/Foxandxss/angular-webpack-workflow)_ 6 | 7 | ##Features 8 | - [x] [Webpack](https://webpack.github.io/) Setup 9 | - [x] [Babel](https://babeljs.io/) 10 | - [x] [Isparta](https://github.com/douglasduteil/isparta) [Instrumenter Loader](https://github.com/ColCh/isparta-instrumenter-loader) 11 | - [x] [Bootstrap](http://getbootstrap.com/) 12 | - [x] Gulp.js Setup 13 | - [x] [Angular Template Cache](https://github.com/miickel/gulp-angular-templatecache) 14 | - [x] [Webpack](https://webpack.github.io/) 15 | - [x] [ESLint](http://eslint.org/blog/2014/11/es6-jsx-support/) 16 | - [x] Basic App Structure by following [Angular Style Guide](https://github.com/johnpapa/angular-styleguide) 17 | - [x] SEO ready configuration using [angular-seo](https://github.com/steeve/angular-seo) 18 | - [x] Full fake REST API using [json-server](https://github.com/typicode/json-server) 19 | - [x] Testing Structure by following [official docs](https://docs.angularjs.org/guide/unit-testing) and [Testing Angular](https://github.com/daniellmb/angular-test-patterns) 20 | - [x] [Karma](http://karma-runner.github.io/0.13/index.html) 21 | - [x] [Jasmine](http://jasmine.github.io/2.0/introduction.html) 22 | - [x] [PhantomJS](http://phantomjs.org/) 23 | - [x] Controller test 24 | - [x] Service test 25 | - [x] Directive test 26 | - [x] Filter Test 27 | - [x] Http interceptor Test 28 | 29 | 30 | ##Install 31 | Clone repo and install npm and bower packages; 32 | 33 | ``` 34 | git clone https://github.com/ziyasal/ng-espack-boilerplate.git 35 | cd ng-espack-boilerplate 36 | npm install 37 | bower install 38 | gulp 39 | ``` 40 | 41 | ## Development 42 | All scripts are run with `npm run [script]`, for example: `npm run test`. 43 | 44 | `build` - generate a minified build to `public` folder 45 | `test` - run all tests 46 | `test:live` - continuously run unit tests watching for changes 47 | `eslint:app` - lint code in `app` folder 48 | `eslint:tests` - lint code in `tests` folder 49 | 50 | See what each script does by looking at the scripts section in `package.json`. 51 | 52 | License 53 | ======= 54 | 55 | Code and documentation are available according to the `MIT` License (see [LICENSE](https://github.com/ziyasal/ng-espack-boilerplate/blob/master/LICENSE)). 56 | -------------------------------------------------------------------------------- /app/app.config.js: -------------------------------------------------------------------------------- 1 | import seoInterceptor from './interceptors/seo-interceptor'; 2 | 3 | export default function config($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { 4 | $locationProvider.hashPrefix('!'); 5 | $urlRouterProvider.otherwise('/'); 6 | 7 | $httpProvider.interceptors.push(seoInterceptor); 8 | } 9 | 10 | config.$inject = ['$stateProvider', '$urlRouterProvider', '$locationProvider', '$httpProvider']; -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import './layout/styles' 2 | 3 | import angular from 'angular'; 4 | import uirouter from 'angular-ui-router'; 5 | 6 | import './templates'; 7 | 8 | import 'lodash'; 9 | import 'angular-animate'; 10 | 11 | import '../bower_components/angular-seo/angular-seo'; 12 | 13 | import config from './app.config'; 14 | import LayoutController from './layout/layout.controller.js'; 15 | 16 | import commonModule from './common'; 17 | import messagesModule from './messages'; 18 | import componentsModule from './components'; 19 | import filtersModule from './filters'; 20 | import servicesModule from './services'; 21 | 22 | angular.module('espackApp', [ 23 | uirouter, 24 | commonModule, componentsModule, filtersModule, servicesModule, messagesModule, 25 | 'templates', 'seo' 26 | ]) 27 | .config(config) 28 | .controller('LayoutController', LayoutController); -------------------------------------------------------------------------------- /app/common/config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function config($provide) { 4 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 5 | } 6 | 7 | 8 | 9 | function extendExceptionHandler($delegate, toaster) { 10 | return function (exception, cause) { 11 | $delegate(exception, cause); 12 | var errorData = { 13 | exception: exception, 14 | cause: cause 15 | }; 16 | 17 | console.log(errorData); 18 | //log errors to remote web server 19 | //toaster.error(exception.msg, errorData); 20 | }; 21 | } 22 | 23 | config.$inject = ['$provide']; 24 | extendExceptionHandler.$inject = ['$delegate']; -------------------------------------------------------------------------------- /app/common/exception.js: -------------------------------------------------------------------------------- 1 | export default function exception(logger) { 2 | return { 3 | catcher 4 | }; 5 | 6 | function catcher(message) { 7 | return function (reason) { 8 | logger.error(message, reason); 9 | }; 10 | } 11 | } 12 | 13 | exception.$inject = ['logger']; -------------------------------------------------------------------------------- /app/common/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import config from './config'; 4 | import Logger from './logger'; 5 | import exception from './exception'; 6 | import toaster from 'angularjs-toaster' 7 | 8 | export default angular.module('espackApp.common', [toaster]) 9 | .config(config) 10 | .service('logger', Logger) 11 | .factory('exception', exception) 12 | .name; -------------------------------------------------------------------------------- /app/common/logger.js: -------------------------------------------------------------------------------- 1 | export default class Logger { 2 | 3 | constructor(toaster){ 4 | this.toaster = toaster; 5 | } 6 | 7 | info(message) { 8 | this.toaster.pop({ 9 | type: 'info', 10 | body: message 11 | }); 12 | } 13 | 14 | error(message) { 15 | this.toaster.pop({ 16 | type: 'error', 17 | body: message 18 | }); 19 | } 20 | } 21 | 22 | Logger.$inject = ['toaster']; 23 | -------------------------------------------------------------------------------- /app/components/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import MessageList from './message-list/message-list.directive'; 4 | 5 | export default angular 6 | .module('espackApp.components', []) 7 | .directive('messageList', ()=>new MessageList) 8 | .name; -------------------------------------------------------------------------------- /app/components/message-list/message-list.directive.html: -------------------------------------------------------------------------------- 1 | 

{{::vm.header}}

2 | 7 | -------------------------------------------------------------------------------- /app/components/message-list/message-list.directive.js: -------------------------------------------------------------------------------- 1 | export default class MessageList { 2 | constructor() { 3 | this.templateUrl = 'components/message-list/message-list.directive.html'; 4 | this.restrict = 'E'; 5 | this.scope = { 6 | messages: '=messages', 7 | header: '=header' 8 | }; 9 | 10 | this.controller = DirectiveController; 11 | this.controllerAs = 'vm'; 12 | this.bindToController = true; 13 | } 14 | 15 | link(scope, attrs) { 16 | // 17 | } 18 | } 19 | 20 | class DirectiveController { 21 | constructor($scope) { 22 | //put your logic here 23 | } 24 | } 25 | 26 | DirectiveController.$inject = ['$scope']; 27 | -------------------------------------------------------------------------------- /app/filters/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import WhiteSpaceFilter from './whitespace.filter.js'; 3 | 4 | export default angular.module('espackApp.filters', []) 5 | .filter('whiteSpace',WhiteSpaceFilter) 6 | .name; -------------------------------------------------------------------------------- /app/filters/whitespace.filter.js: -------------------------------------------------------------------------------- 1 | export default function WhiteSpaceFilter(){ 2 | 3 | return (text)=>{ 4 | 5 | if(!!text){ 6 | 7 | let result = text.replace(/\s/g, ''); 8 | return result; 9 | } 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /app/interceptors/seo-interceptor.js: -------------------------------------------------------------------------------- 1 | export default function seoInterceptor($q, $injector) { 2 | 3 | let $http; 4 | 5 | return { 6 | response: (response) => { 7 | $http = $http || $injector.get('$http'); 8 | let $timeout = $injector.get('$timeout'); 9 | let $rootScope = $injector.get('$rootScope'); 10 | 11 | if ($http.pendingRequests.length < 1) { 12 | $timeout(()=> { 13 | if ($http.pendingRequests.length < 1) { 14 | $rootScope.htmlReady(); 15 | console.log('[HTML] ready.'); 16 | } 17 | }, 700); 18 | /*an 0.7 seconds safety interval, if there are no requests for 0.7 seconds, it means that the layout is through rendering*/ 19 | } 20 | return response || $q.when(response); 21 | }, 22 | responseError: (response) => { 23 | return $q.reject(response); 24 | } 25 | }; 26 | } 27 | 28 | seoInterceptor.$inject = ['$q', '$injector']; -------------------------------------------------------------------------------- /app/layout/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Override the default bootstrap behavior where horizontal description lists 13 | will truncate terms that are too long to fit in the left column 14 | */ 15 | .dl-horizontal dt { 16 | white-space: normal; 17 | } 18 | 19 | /* Set width on the form input elements since they're 100% wide by default */ 20 | input, 21 | select, 22 | textarea { 23 | max-width: 280px; 24 | } 25 | -------------------------------------------------------------------------------- /app/layout/layout.controller.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | export default class LayoutController { 4 | constructor($scope) { 5 | this.pageTitle = 'AngularJS + ES6 application using Webpack'; 6 | 7 | $scope.$on('$stateChangeSuccess', (event, toState, toParams, fromState, fromParams)=> { 8 | if (angular.isDefined(toState.data.pageTitle)) { 9 | this.pageTitle = `${toState.data.pageTitle} | AngularJS + ES6 application using Webpack`; 10 | } 11 | }); 12 | } 13 | } 14 | 15 | LayoutController.$inject = ['$scope']; 16 | -------------------------------------------------------------------------------- /app/layout/styles.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | import 'angularjs-toaster/toaster.css'; 3 | import './app.css'; -------------------------------------------------------------------------------- /app/messages/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import uirouter from 'angular-ui-router'; 3 | 4 | import config from './messages.config'; 5 | 6 | import MessagesController from './messages.controller.js'; 7 | 8 | export default angular.module('espackApp.messages', [uirouter]) 9 | .config(config) 10 | .controller('MessagesController', MessagesController) 11 | .name; -------------------------------------------------------------------------------- /app/messages/list.html: -------------------------------------------------------------------------------- 1 | 

AngularJS + ES6 boilerplate application using Webpack

2 | 3 | -------------------------------------------------------------------------------- /app/messages/messages.config.js: -------------------------------------------------------------------------------- 1 | export default function config($stateProvider) { 2 | $stateProvider.state('messages', { 3 | url: '/', 4 | views: { 5 | main: { 6 | controller: 'MessagesController', 7 | templateUrl: 'messages/list.html', 8 | controllerAs: 'vm' 9 | } 10 | }, 11 | data: { 12 | pageTitle: 'Home' 13 | } 14 | }); 15 | } 16 | 17 | config.$inject = ['$stateProvider']; -------------------------------------------------------------------------------- /app/messages/messages.controller.js: -------------------------------------------------------------------------------- 1 | export default class MessagesController { 2 | constructor($scope, logger, messageService) { 3 | this.messages = []; 4 | this.messageService = messageService; 5 | this.logger = logger; 6 | 7 | this.activate(); 8 | } 9 | 10 | activate() { 11 | return this.loadMessages().then(()=> { 12 | this.logger.info('init Home View'); 13 | }); 14 | } 15 | 16 | 17 | loadMessages() { 18 | return this.messageService.findAll().then(response=> { 19 | this.messages = response; 20 | 21 | return this.messages; 22 | }); 23 | } 24 | } 25 | 26 | MessagesController.$inject = ['$scope', 'logger', 'messageService']; 27 | -------------------------------------------------------------------------------- /app/services/index.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | import MessageService from './message.service.js'; 4 | 5 | export default angular.module('espackApp.services', []) 6 | .service('messageService', MessageService) 7 | .name; -------------------------------------------------------------------------------- /app/services/message.service.js: -------------------------------------------------------------------------------- 1 | export default class MessageService { 2 | 3 | constructor($http, $q, logger) { 4 | this.$http = $http; 5 | this.$q = $q; 6 | this.logger = logger; 7 | } 8 | 9 | findAll() { 10 | var deferred = this.$q.defer(); 11 | 12 | this.$http.get('http://localhost:3000/messages') 13 | .success((data, status, headers, config) => { 14 | deferred.resolve(data); 15 | }) 16 | .error((data, status)=> { 17 | this.logger.error('XHR Failed for MessageService#findAll.' + data); 18 | deferred.reject(data); 19 | }); 20 | 21 | return deferred.promise; 22 | } 23 | 24 | find(id) { 25 | var deferred = this.$q.defer(); 26 | 27 | this.$http.get(`http://localhost:3000/messages/${id}`) 28 | .success((data, status, headers, config)=> { 29 | deferred.resolve(data); 30 | }) 31 | .error((data, status) => { 32 | this.logger.error('XHR Failed for MessageService#find.' + data); 33 | deferred.reject(data); 34 | }); 35 | 36 | return deferred.promise; 37 | } 38 | } 39 | 40 | MessageService.$inject = ['$http', '$q', 'logger']; -------------------------------------------------------------------------------- /app/templates.js: -------------------------------------------------------------------------------- 1 | angular.module("templates", []).run(["$templateCache", function($templateCache) {$templateCache.put("messages/list.html","

AngularJS + ES6 boilerplate application using Webpack

\n\n"); 2 | $templateCache.put("components/message-list/message-list.directive.html","

{{::vm.header}}

\n\n");}]); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-espack-boilerplate", 3 | "version": "1.0.0", 4 | "authors": [ 5 | "ziyasal" 6 | ], 7 | "license": "MIT", 8 | "ignore": [ 9 | "**/.*", 10 | "node_modules", 11 | "bower_components", 12 | "test", 13 | "tests" 14 | ], 15 | "dependencies": { 16 | "bootstrap": "~3.3.6", 17 | "modernizr": "~3.2.0", 18 | "respond": "~1.4.2", 19 | "angular-seo": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /e2e-tests/protractor.conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | 4 | specs: [ 5 | '*.js' 6 | ], 7 | 8 | capabilities: { 9 | 'browserName': 'chrome' 10 | }, 11 | 12 | baseUrl: 'http://localhost:8080/#!/', 13 | 14 | framework: 'jasmine', 15 | 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 30000 18 | } 19 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 |  2 | var gulp = require('gulp'); 3 | var templateCache = require('gulp-angular-templatecache'); 4 | var gutil = require('gulp-util'); 5 | 6 | var webpack = require('webpack'); 7 | var webpackConfig = require('./webpack.config.js'); 8 | var WebpackDevServer = require('webpack-dev-server'); 9 | 10 | var webpackDevCfg = Object.create(webpackConfig); 11 | webpackDevCfg.devtool = 'eval-source-map'; 12 | webpackDevCfg.debug = true; 13 | webpackDevCfg.output.pathinfo = true; 14 | webpackDevCfg.cache = true; 15 | webpackDevCfg.watch = true; 16 | 17 | var devCompiler = webpack(webpackDevCfg); 18 | 19 | gulp.task('tpl-cache', function() { 20 | return gulp.src('app/**/*html') 21 | .pipe(templateCache({ 22 | standalone: true 23 | })) 24 | .pipe(gulp.dest('app')); 25 | }); 26 | 27 | gulp.task('watch:html', function() { 28 | return gulp.watch(['app/**/*.html'], ['tpl-cache']); 29 | }); 30 | 31 | 32 | gulp.task('webpack:dev-server', function(callback) { 33 | 34 | new WebpackDevServer(devCompiler, { 35 | contentBase: 'public', 36 | hot: true, 37 | filename: 'bundle.js', 38 | watchOptions: { 39 | aggregateTimeout: 300, 40 | poll: 1000 41 | } 42 | }).listen(8080, 'localhost', function(err) { 43 | if (err) throw new gutil.PluginError('webpack-dev-server', err); 44 | 45 | gutil.log('[webpack-dev-server]', 'http://localhost:8080/webpack-dev-server/index.html'); 46 | 47 | callback(); 48 | }); 49 | }); 50 | 51 | gulp.task('json-server', function() { 52 | require('./index'); 53 | }); 54 | 55 | gulp.task('default', ['json-server', 'watch:html', 'webpack:dev-server']); 56 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var jsonServer = require('json-server'); 2 | 3 | var server = jsonServer.create(); 4 | server.use(jsonServer.defaults()); 5 | 6 | var router = jsonServer.router('./public/db.json'); 7 | server.use(router); 8 | 9 | server.listen(3000); -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Dec 08 2015 16:01:19 GMT+0200 (Turkey Standard Time) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // frameworks to use 8 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 9 | frameworks: ['jasmine'], 10 | 11 | // test results reporter to use 12 | // possible values: 'dots', 'progress' 13 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 14 | reporters: [ 15 | 'progress', 16 | // Reference: https://github.com/mlex/karma-spec-reporter 17 | // Set reporter to print detailed results to console 18 | 'spec', 19 | 20 | // Reference: https://github.com/karma-runner/karma-coverage 21 | // Output code coverage files 22 | 'coverage' 23 | ], 24 | coverageReporter: { 25 | type: 'lcov', 26 | dir: 'coverage', 27 | subdir: '.' 28 | }, 29 | 30 | // list of files / patterns to load in the browser 31 | files: [ 32 | 'tests/tests.webpack.js' 33 | ], 34 | 35 | // list of files to exclude 36 | exclude: [], 37 | 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | 'tests/tests.webpack.js': ['webpack', 'sourcemap'] 43 | }, 44 | 45 | // web server port 46 | port: 9876, 47 | 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | 53 | // level of logging 54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 55 | logLevel: config.LOG_INFO, 56 | 57 | 58 | // enable / disable watching file and executing tests whenever any file changes 59 | autoWatch: true, 60 | 61 | 62 | // start these browsers 63 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 64 | browsers: ['PhantomJS'], 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, Karma captures browsers, runs the tests and exits 69 | singleRun: true, 70 | 71 | // Concurrency level 72 | // how many browser should be started simultanous 73 | concurrency: Infinity, 74 | 75 | webpack: require('./webpack.test.js'), 76 | 77 | // Hide webpack build information from output 78 | webpackMiddleware: { 79 | noInfo: true 80 | } 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-espack-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Angular 1.4x + ES6 boilerplate application using Webpack", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.build.js --bail -p", 8 | "test": "karma start", 9 | "test:live": "karma start --auto-watch --no-single-run", 10 | "preupdate-webdriver": "npm install", 11 | "update-webdriver": "webdriver-manager update", 12 | "preprotractor": "npm run update-webdriver", 13 | "protractor": "protractor e2e-tests/protractor.conf.js", 14 | "eslint:app": "eslint app/**", 15 | "eslint:tests": "eslint tests/**", 16 | "test-travis": "karma start && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/ziyasal/ng-espack-boilerplate.git" 21 | }, 22 | "keywords": [ 23 | "angular", 24 | "webpack", 25 | "es6", 26 | "babel", 27 | "isparta", 28 | "boilerplate", 29 | "eslint", 30 | "jasmine", 31 | "karma" 32 | ], 33 | "author": "ziyasal", 34 | "license": "MIT", 35 | "devDependencies": { 36 | "angular-mocks": "1.4.8", 37 | "autoprefixer": "6.0.3", 38 | "babel-core": "6.2.1", 39 | "babel-loader": "6.2.0", 40 | "babel-preset-es2015": "6.1.18", 41 | "chai": "3.4.1", 42 | "css-loader": "0.23.0", 43 | "eslint": "1.10.3", 44 | "eslint-config-defaults": "7.1.1", 45 | "extract-text-webpack-plugin": "0.9.1", 46 | "file-loader": "0.8.4", 47 | "gulp": "3.9.0", 48 | "gulp-angular-templatecache": "1.8.0", 49 | "gulp-concat": "2.6.0", 50 | "gulp-livereload": "3.8.1", 51 | "gulp-sourcemaps": "1.6.0", 52 | "gulp-uglify": "1.5.1", 53 | "gulp-usemin": "0.3.16", 54 | "gulp-util": "3.0.7", 55 | "html-webpack-plugin": "1.6.2", 56 | "istanbul-instrumenter-loader": "1.0.0", 57 | "jasmine-core": "2.4.1", 58 | "json-server": "0.8.4", 59 | "karma": "0.13.15", 60 | "karma-chrome-launcher": "0.2.2", 61 | "karma-coverage": "0.5.3", 62 | "karma-jasmine": "0.3.6", 63 | "karma-phantomjs-launcher": "0.2.1", 64 | "karma-sourcemap-loader": "0.3.6", 65 | "karma-spec-reporter": "0.0.23", 66 | "karma-webpack": "1.7.0", 67 | "node-libs-browser": "0.5.3", 68 | "null-loader": "0.1.1", 69 | "phantomjs": "1.9.18", 70 | "postcss-loader": "0.8.0", 71 | "raw-loader": "0.5.1", 72 | "run-sequence": "1.1.5", 73 | "style-loader": "0.13.0", 74 | "webpack": "1.12.2", 75 | "webpack-dev-server": "1.12.1" 76 | }, 77 | "dependencies": { 78 | "angular": "1.5.9", 79 | "angular-animate": "1.4.8", 80 | "angular-ui-router": "0.2.18", 81 | "angularjs-toaster": "0.4.15", 82 | "bootstrap": "3.3.7", 83 | "jquery": "2.2.4", 84 | "lodash": "4.13.1", 85 | "phantomjs": "1.9.20" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /public/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": [ 3 | { 4 | "id": 1, 5 | "text": "Hi, Nikola Tesla" 6 | }, 7 | { 8 | "id": 2, 9 | "text": "Hi, James Clerk Maxwell" 10 | }, 11 | { 12 | "id": 3, 13 | "text": "Hi, Isaac Asimov" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | angular es6 webpack app 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/common/exception.spec.js: -------------------------------------------------------------------------------- 1 | describe('exception', ()=> { 2 | 3 | let exceptionInstance, loggerMock; 4 | 5 | beforeEach(()=> { 6 | angular.mock.module(function ($provide) { 7 | $provide.service('logger', function () { 8 | this.error = jasmine.createSpy('error'); 9 | }); 10 | }); 11 | 12 | angular.mock.module('espackApp'); 13 | }); 14 | 15 | describe('#ctor', ()=> { 16 | 17 | beforeEach(()=> { 18 | _inject(); 19 | }); 20 | 21 | it('should exist', ()=> { 22 | expect(!!exceptionInstance).toBe(true); 23 | }); 24 | 25 | it('should define catcher property', function () { 26 | 27 | expect(exceptionInstance.catcher).toBeDefined(); 28 | expect(typeof exceptionInstance.catcher).toBe('function'); 29 | }); 30 | 31 | }); 32 | 33 | function _inject() { 34 | inject((exception, logger) => { 35 | loggerMock = logger; 36 | exceptionInstance = exception; 37 | }); 38 | } 39 | }); -------------------------------------------------------------------------------- /tests/common/logger.spec.js: -------------------------------------------------------------------------------- 1 | describe('Logger', ()=> { 2 | 3 | let loggerInstance, toaster, toasterMock; 4 | 5 | beforeEach(()=> { 6 | angular.mock.module(function ($provide) { 7 | $provide.service('toaster', function () { 8 | this.pop = jasmine.createSpy('pop'); 9 | }); 10 | }); 11 | 12 | angular.mock.module('espackApp'); 13 | }); 14 | 15 | describe('#ctor', ()=> { 16 | 17 | beforeEach(()=> { 18 | _inject(); 19 | }); 20 | 21 | it('should exist', ()=> { 22 | expect(!!loggerInstance).toBe(true); 23 | }); 24 | 25 | }); 26 | 27 | describe('#info', ()=> { 28 | 29 | beforeEach(()=> { 30 | _inject(); 31 | }); 32 | 33 | it('should be defined', ()=> { 34 | expect(loggerInstance.info).toBeDefined(); 35 | }); 36 | 37 | it('should be defined', ()=> { 38 | 39 | let expectedMessage = 'TEST'; 40 | 41 | spyOn(toasterMock, 'pop').and.callThrough(); 42 | 43 | loggerInstance.info(expectedMessage); 44 | 45 | expect(toasterMock.pop).toHaveBeenCalledWith({type: 'info', body: expectedMessage}); 46 | 47 | }); 48 | }); 49 | 50 | function _inject() { 51 | inject((logger, toaster) => { 52 | loggerInstance = logger; 53 | toasterMock = toaster; 54 | }); 55 | } 56 | }); -------------------------------------------------------------------------------- /tests/components/message-list.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('MessageListDirective', ()=> { 2 | 3 | let element, scope, compile, rootScope, 4 | validTemplate = '', 5 | defaultData = [ 6 | { 7 | id: 0, 8 | text: 'Hi, Loretta Fitzgerald' 9 | } 10 | ]; 11 | 12 | beforeEach(angular.mock.module('espackApp')); 13 | 14 | beforeEach(()=> { 15 | _inject(); 16 | }); 17 | 18 | describe('when created', function () { 19 | it('should not render list when messages property is empty', function () { 20 | element = createDirective(null, ''); 21 | var expected = element.find('li').size(); 22 | expect(expected).toBe(0); 23 | }); 24 | 25 | it('should render the expected output', function () { 26 | element = createDirective(); 27 | var expected = element.find('li').first().text().trim(); 28 | expect(expected).toBe(defaultData[0].text); 29 | }); 30 | }); 31 | 32 | describe('when the model changes', function () { 33 | // Add specs 34 | }); 35 | 36 | describe('when destroyed', function () { 37 | // Add specs 38 | }); 39 | 40 | 41 | function createDirective(data, template) { 42 | let elm; 43 | 44 | //setup scope state 45 | scope.vm = {messages: data || defaultData}; 46 | 47 | //create directive 48 | elm = compile(template || validTemplate)(scope); 49 | 50 | //trigger watchers 51 | //scope.$apply(); 52 | scope.$digest(); 53 | return elm; 54 | } 55 | 56 | function _inject() { 57 | inject(($rootScope, $compile)=> { 58 | scope = $rootScope.$new(); 59 | compile = $compile; 60 | rootScope = $rootScope; 61 | }); 62 | } 63 | }); -------------------------------------------------------------------------------- /tests/filters/whitespace.filter.spec.js: -------------------------------------------------------------------------------- 1 | describe('WhiteSpaceFilter', ()=> { 2 | 3 | let filterFactory, filterInstance; 4 | 5 | beforeEach(()=> { 6 | angular.mock.module('espackApp'); 7 | }); 8 | 9 | describe('#ctor', ()=> { 10 | 11 | beforeEach(()=> { 12 | _inject(); 13 | }); 14 | 15 | it('should exist', ()=> { 16 | expect(!!filterFactory).toBe(true); 17 | }); 18 | 19 | }); 20 | 21 | describe('#whiteSpaceFilter', ()=> { 22 | 23 | beforeEach(()=> { 24 | _inject(); 25 | }); 26 | 27 | it('should return same value', ()=> { 28 | let actual = 'Sample Text'; 29 | filterInstance = filterFactory('whiteSpace'); 30 | 31 | var expected = 'SampleText'; 32 | expect(filterInstance(actual)).toEqual(expected); 33 | }); 34 | 35 | }); 36 | 37 | function _inject() { 38 | inject($filter=> { 39 | filterFactory = $filter; 40 | }); 41 | } 42 | }); -------------------------------------------------------------------------------- /tests/interceptors/seo-interceptor.spec.js: -------------------------------------------------------------------------------- 1 | describe("seoInterceptor", function () { 2 | 3 | const seoInterceptorName = "seoInterceptor"; 4 | 5 | let seoInterceptor, httpProvider, httpBackend; 6 | beforeEach(angular.mock.module('espackApp', ($httpProvider)=> { 7 | httpProvider = $httpProvider; 8 | })); 9 | 10 | describe('#existence', () => { 11 | 12 | beforeEach(()=> { 13 | _inject(); 14 | }); 15 | 16 | it('should be added to $http interceptors', ()=> { 17 | 18 | var interceptorNames = httpProvider.interceptors 19 | .filter((item)=> item.prototype.constructor.name === seoInterceptorName) 20 | .map((item)=>item.prototype.constructor.name); 21 | 22 | expect(interceptorNames.length).toEqual(1); 23 | expect(interceptorNames).toEqual([seoInterceptorName]) 24 | 25 | }); 26 | 27 | }); 28 | 29 | function _inject() { 30 | inject(($httpBackend) => { 31 | httpBackend = $httpBackend; 32 | }); 33 | } 34 | 35 | }); -------------------------------------------------------------------------------- /tests/layout/layout-controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('LayoutController', ()=> { 2 | 3 | let LayoutController, mock, rootScope, controllerFactory; 4 | 5 | const pageTitleSuffix = ' | AngularJS + ES6 application using Webpack'; 6 | 7 | beforeEach(angular.mock.module('espackApp')); 8 | 9 | describe('#construct', ()=> { 10 | 11 | beforeEach(()=> { 12 | _inject(); 13 | }); 14 | 15 | it('should exist', ()=> { 16 | LayoutController = controllerFactory('LayoutController', mock); 17 | expect(!!LayoutController).toBe(true); 18 | }); 19 | 20 | it('should define pageTitle property', ()=> { 21 | LayoutController = controllerFactory('LayoutController', mock); 22 | expect(LayoutController.pageTitle).toBeDefined(); 23 | }); 24 | 25 | it('should set pageTitle by handling $stateChangeSuccess event', function () { 26 | 27 | let stateData = { 28 | data: {pageTitle: 'TEST'} 29 | }; 30 | 31 | LayoutController = controllerFactory('LayoutController', mock); 32 | rootScope.$broadcast('$stateChangeSuccess', stateData); 33 | 34 | expect(LayoutController.pageTitle).toBe(stateData.data.pageTitle + pageTitleSuffix) 35 | }); 36 | }); 37 | 38 | function _inject() { 39 | inject(($controller, $rootScope) => { 40 | rootScope = $rootScope; 41 | controllerFactory = $controller; 42 | 43 | mock = { 44 | $scope: $rootScope.$new() 45 | }; 46 | }); 47 | } 48 | }); -------------------------------------------------------------------------------- /tests/messages/messages-controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('MessagesController', ()=> { 2 | 3 | let homeCtrl, mock, deferredResolution, rootScope, controllerFactory, 4 | mockData = [ 5 | { 6 | id: 0, 7 | text: 'Hi, Loretta Fitzgerald' 8 | } 9 | ]; 10 | 11 | beforeEach(angular.mock.module('espackApp')); 12 | 13 | describe('#construct', ()=> { 14 | 15 | beforeEach(()=> { 16 | _inject(); 17 | }); 18 | 19 | it('should exist', ()=> { 20 | homeCtrl = controllerFactory('MessagesController', mock); 21 | expect(!!homeCtrl).toBe(true); 22 | }); 23 | 24 | it('should define a messages Array property', () => { 25 | homeCtrl = controllerFactory('MessagesController', mock); 26 | expect(homeCtrl.messages instanceof Array).toBe(true); 27 | }); 28 | }); 29 | 30 | describe('#init', ()=> { 31 | 32 | beforeEach(()=> { 33 | _inject(); 34 | }); 35 | 36 | it('should be defined', ()=> { 37 | homeCtrl = controllerFactory('MessagesController', mock); 38 | expect(typeof homeCtrl.activate).toBe('function'); 39 | }); 40 | 41 | it('should set messages property', () => { 42 | //given 43 | spyOn(mock.messageService, 'findAll').and.returnValue(deferredResolution.promise); 44 | 45 | //when 46 | deferredResolution.resolve(mockData); 47 | homeCtrl = controllerFactory('MessagesController', mock); 48 | rootScope.$digest(); 49 | 50 | //then 51 | expect(homeCtrl.messages.map(item=> item.text)).toEqual(mockData.map(item=> item.text)); 52 | expect(mock.messageService.findAll).toHaveBeenCalled(); 53 | }); 54 | }); 55 | 56 | describe('#loadMessages', ()=> { 57 | 58 | beforeEach(()=> { 59 | _inject(); 60 | }); 61 | 62 | it('should set messages property', () => { 63 | spyOn(mock.messageService, 'findAll').and.returnValue(deferredResolution.promise); 64 | homeCtrl = controllerFactory('MessagesController', mock); 65 | homeCtrl.init = ()=> { 66 | };//skip initialization because of testing loadMessages 67 | 68 | homeCtrl.loadMessages(); 69 | deferredResolution.resolve(mockData); 70 | rootScope.$digest(); 71 | 72 | expect(homeCtrl.messages.map(item=> item.text)).toEqual(mockData.map(item => item.text)); 73 | expect(mock.messageService.findAll).toHaveBeenCalled(); 74 | }); 75 | 76 | }); 77 | 78 | function _inject() { 79 | inject(($controller, $rootScope, messageService, logger, $q) => { 80 | rootScope = $rootScope; 81 | controllerFactory = $controller; 82 | deferredResolution = $q.defer(); 83 | mock = { 84 | $scope: $rootScope.$new(), 85 | logger, 86 | messageService 87 | }; 88 | }); 89 | } 90 | }); -------------------------------------------------------------------------------- /tests/services/message.service.spec.js: -------------------------------------------------------------------------------- 1 | describe('MessageService', ()=> { 2 | 3 | let messageSvc, httpBackend, loggerMock, 4 | mockData = [ 5 | { 6 | id: 0, 7 | text: 'Hi, Loretta Fitzgerald' 8 | } 9 | ], 10 | mockErrorData = {'msg': 'An error occurred'}; 11 | 12 | beforeEach(()=> { 13 | 14 | angular.mock.module(function ($provide) { 15 | $provide.service('logger', function () { 16 | this.error = jasmine.createSpy('error'); 17 | }); 18 | }); 19 | 20 | angular.mock.module('espackApp'); 21 | }); 22 | 23 | describe('#ctor', ()=> { 24 | 25 | beforeEach(()=> { 26 | _inject(); 27 | }); 28 | 29 | it('should exist', ()=> { 30 | expect(!!messageSvc).toBe(true); 31 | }); 32 | 33 | }); 34 | 35 | describe('#findAll', ()=> { 36 | 37 | beforeEach(()=> { 38 | _inject(); 39 | }); 40 | 41 | it('should be defined', ()=> { 42 | expect(typeof messageSvc.findAll).toBe('function'); 43 | }); 44 | 45 | it('should make $http call', ()=> { 46 | 47 | let expectedData; 48 | 49 | //given 50 | httpBackend.whenGET('http://localhost:3000/messages').respond(200, mockData); 51 | 52 | //when 53 | messageSvc.findAll().then((data, status, headers, config)=> { 54 | expectedData = data; 55 | }); 56 | 57 | httpBackend.flush(); 58 | 59 | //then 60 | expect(expectedData.map((elm)=> elm.text)).toEqual(mockData.map((elm)=> elm.text)); 61 | }); 62 | 63 | it('should handle when $http call failed', ()=> { 64 | 65 | let expectedData, expectedMessage = 'XHR Failed for MessageService#findAll.' + mockErrorData; 66 | 67 | //given 68 | spyOn(loggerMock, 'error').and.callThrough(); 69 | httpBackend.whenGET('http://localhost:3000/messages').respond(400, mockErrorData); 70 | 71 | //when 72 | messageSvc.findAll().then(()=> { 73 | }, (reason)=> { 74 | expectedData = reason; 75 | }); 76 | 77 | httpBackend.flush(); 78 | 79 | //then 80 | expect(expectedData.msg).toEqual(mockErrorData.msg); 81 | expect(loggerMock.error).toHaveBeenCalledWith(expectedMessage); 82 | }); 83 | 84 | }); 85 | 86 | describe('#find', ()=> { 87 | 88 | beforeEach(()=> { 89 | _inject(); 90 | }); 91 | 92 | it('should be defined', ()=> { 93 | expect(typeof messageSvc.find).toBe('function'); 94 | }); 95 | 96 | it('should make $http call', ()=> { 97 | 98 | let expectedData, mockId = 1; 99 | 100 | //given 101 | httpBackend.whenGET(`http://localhost:3000/messages/${mockId}`).respond(200, mockData[0]); 102 | 103 | //when 104 | messageSvc.find(mockId).then((data, status, headers, config)=> { 105 | expectedData = data; 106 | }); 107 | 108 | httpBackend.flush(); 109 | 110 | //then 111 | expect(expectedData.text).toEqual(mockData[0].text); 112 | }); 113 | 114 | it('should handle when $http call failed', ()=> { 115 | 116 | let expectedData, 117 | expectedMessage = 'XHR Failed for MessageService#find.' + mockErrorData, 118 | mockId = 1; 119 | 120 | //given 121 | spyOn(loggerMock, 'error').and.callThrough(); 122 | httpBackend.whenGET(`http://localhost:3000/messages/${mockId}`).respond(400, mockErrorData); 123 | 124 | //when 125 | messageSvc.find(mockId).then(()=> { 126 | }, (reason)=> { 127 | expectedData = reason; 128 | }); 129 | 130 | httpBackend.flush(); 131 | 132 | //then 133 | expect(expectedData.msg).toEqual(mockErrorData.msg); 134 | expect(loggerMock.error).toHaveBeenCalledWith(expectedMessage); 135 | }); 136 | }); 137 | 138 | afterEach(function () { 139 | httpBackend.verifyNoOutstandingExpectation(); 140 | httpBackend.verifyNoOutstandingRequest(); 141 | }); 142 | 143 | function _inject() { 144 | inject((messageService, $httpBackend, logger)=> { 145 | 146 | messageSvc = messageService; 147 | httpBackend = $httpBackend; 148 | loggerMock = logger; 149 | 150 | }); 151 | } 152 | }); -------------------------------------------------------------------------------- /tests/tests.webpack.js: -------------------------------------------------------------------------------- 1 | import 'angular'; 2 | import '../app/app'; 3 | import 'angular-mocks/angular-mocks'; 4 | 5 | var testsContext = require.context('.', true, /.spec$/); 6 | testsContext.keys().forEach(testsContext); -------------------------------------------------------------------------------- /webpack.build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for builds 3 | */ 4 | module.exports = require('./webpack.make.js')({ 5 | BUILD: true, 6 | TEST: false 7 | }); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for development 3 | */ 4 | module.exports = require('./webpack.make.js')({ 5 | BUILD: false, 6 | TEST: false 7 | }); -------------------------------------------------------------------------------- /webpack.make.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Modules 4 | var webpack = require('webpack'); 5 | var autoprefixer = require('autoprefixer'); 6 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | 9 | module.exports = function makeWebpackConfig(options) { 10 | /** 11 | * Environment type 12 | * BUILD is for generating minified builds 13 | * TEST is for generating test builds 14 | */ 15 | var BUILD = !!options.BUILD; 16 | var TEST = !!options.TEST; 17 | 18 | var config = {}; 19 | 20 | if (TEST) { 21 | config.entry = {} 22 | } else { 23 | config.entry = { 24 | app: './app/app.js' 25 | } 26 | } 27 | 28 | if (TEST) { 29 | config.output = {} 30 | } else { 31 | config.output = { 32 | // Absolute output directory 33 | path: __dirname + '/public', 34 | 35 | publicPath: BUILD ? '/' : 'http://localhost:8080/', 36 | 37 | filename: BUILD ? '[name].[hash].js' : '[name].bundle.js', 38 | //filename: 'bundle.js', 39 | 40 | chunkFilename: BUILD ? '[name].[hash].js' : '[name].bundle.js' 41 | //chunkFilename: BUILD ? 'bundle.js' : 'bundle.js' 42 | } 43 | } 44 | 45 | if (TEST) { 46 | config.devtool = 'inline-source-map'; 47 | } else if (BUILD) { 48 | config.devtool = 'source-map'; 49 | } else { 50 | config.devtool = 'eval-source-map'; 51 | config.debug = false; 52 | config.output.pathinfo = true; 53 | config.cache = true; 54 | config.watch = true; 55 | } 56 | 57 | config.module = { 58 | preLoaders: [], 59 | loaders: [{ 60 | test: /\.js$/, 61 | loader: 'babel', 62 | exclude: /node_modules/ 63 | }, { 64 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/, 65 | loader: 'file' 66 | }, { 67 | test: /\.html$/, 68 | loader: 'raw' 69 | }] 70 | }; 71 | 72 | if (TEST) { 73 | config.module.preLoaders.push({ 74 | test: /\.js$/, 75 | exclude: [ 76 | /node_modules/, 77 | /bower_components/, 78 | /\.spec\.js$/, 79 | /tests/ 80 | ], 81 | loader: 'isparta-instrumenter' 82 | }); 83 | } 84 | 85 | var cssLoader = { 86 | test: /\.css$/, 87 | loader: ExtractTextPlugin.extract('style', 'css?sourceMap!postcss') 88 | }; 89 | 90 | if (TEST) { 91 | cssLoader.loader = 'null'; 92 | } 93 | 94 | config.module.loaders.push(cssLoader); 95 | 96 | config.postcss = [ 97 | autoprefixer({ 98 | browsers: ['last 2 version'] 99 | }) 100 | ]; 101 | 102 | config.plugins = [ 103 | new ExtractTextPlugin('[name].[hash].css', { 104 | disable: !BUILD || TEST 105 | }), 106 | 107 | // Provide jquery 108 | new webpack.ProvidePlugin({ 109 | $: 'jquery', 110 | jQuery: 'jquery', 111 | 'window.jQuery': 'jquery' 112 | }) 113 | ]; 114 | 115 | if (!TEST) { 116 | config.plugins.push( 117 | new HtmlWebpackPlugin({ 118 | template: './public/index.html', 119 | inject: 'body' 120 | }) 121 | ) 122 | } 123 | 124 | if (BUILD) { 125 | config.plugins.push( 126 | new webpack.NoErrorsPlugin(), 127 | new webpack.optimize.DedupePlugin(), 128 | new webpack.optimize.UglifyJsPlugin() 129 | ); 130 | } 131 | 132 | return config; 133 | }; 134 | -------------------------------------------------------------------------------- /webpack.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for tests 3 | */ 4 | module.exports = require('./webpack.make.js')({ 5 | BUILD: false, 6 | TEST: true 7 | }); --------------------------------------------------------------------------------