├── .babelrc
├── .gitignore
├── .travis.yml
├── README.md
├── karma.conf.js
├── package.json
├── src
├── app
│ ├── app.config.js
│ ├── app.js
│ ├── features
│ │ ├── home
│ │ │ ├── home.controller.js
│ │ │ ├── home.controller.test.js
│ │ │ ├── home.css
│ │ │ ├── home.html
│ │ │ ├── home.routes.js
│ │ │ └── index.js
│ │ └── login
│ │ │ ├── index.js
│ │ │ ├── login.controller.js
│ │ │ ├── login.controller.test.js
│ │ │ ├── login.css
│ │ │ ├── login.html
│ │ │ └── login.routes.js
│ └── services
│ │ ├── auth.service.js
│ │ ├── auth.service.test.js
│ │ ├── words.service.js
│ │ └── words.service.test.js
├── public
│ ├── img
│ │ ├── favicon.ico
│ │ └── german-flag.jpg
│ └── index.html
└── tests.webpack.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/
3 | build
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 5.5
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular 1.x + ES6 + Webpack 2 Demo
2 |
3 | This repository contains a demo for the [angular-webpack](https://github.com/preboot/angular-webpack).
4 |
5 | ## Usage
6 |
7 | This repository needs a backend, refer to [GermanWords-koa](https://github.com/angular-tips/GermanWords-backend-koa) for further information in how to use it. Once you have it running:
8 |
9 | ```shell
10 | $ git clone https://github.com/Foxandxss/GermanWords-ng1-webpack
11 | $ cd GermanWords-ng1-webpack
12 | $ npm install
13 | $ npm start
14 | ```
15 |
16 | Now you can navigate to http://localhost:8080 to see the app working.
17 |
18 | The username and password for the demo is: `demo / 12345`.
19 |
20 | ## Tests
21 |
22 | If you want to run the tests, do:
23 |
24 | ```shell
25 | $ npm test
26 | ```
27 |
28 | ## Deploy
29 |
30 | For deployment:
31 |
32 | ```shell
33 | $ npm run build
34 | ```
35 |
36 | That will generate a `/dist` folder where you can point your web server.
37 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Reference: http://karma-runner.github.io/0.12/config/configuration-file.html
2 | module.exports = function karmaConfig (config) {
3 | config.set({
4 | frameworks: [
5 | // Reference: https://github.com/karma-runner/karma-jasmine
6 | // Set framework to jasmine
7 | 'jasmine'
8 | ],
9 |
10 | reporters: [
11 | // Reference: https://github.com/mlex/karma-spec-reporter
12 | // Set reporter to print detailed results to console
13 | 'progress',
14 |
15 | // Reference: https://github.com/karma-runner/karma-coverage
16 | // Output code coverage files
17 | 'coverage'
18 | ],
19 |
20 | files: [
21 | // Grab all files in the app folder that contain .spec.
22 | 'src/tests.webpack.js'
23 | ],
24 |
25 | preprocessors: {
26 | // Reference: http://webpack.github.io/docs/testing.html
27 | // Reference: https://github.com/webpack/karma-webpack
28 | // Convert files with webpack and load sourcemaps
29 | 'src/tests.webpack.js': ['webpack', 'sourcemap']
30 | },
31 |
32 | browsers: [
33 | // Run tests using PhantomJS2
34 | 'PhantomJS2'
35 | ],
36 |
37 | singleRun: true,
38 |
39 | // Configure code coverage reporter
40 | coverageReporter: {
41 | dir: 'build/coverage/',
42 | reporters: [
43 | {type: 'text-summary'},
44 | {type: 'html'}
45 | ]
46 | },
47 |
48 | webpack: require('./webpack.config')('test'),
49 |
50 | // Hide webpack build information from output
51 | webpackMiddleware: {
52 | noInfo: true
53 | }
54 | });
55 | };
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "germanwords-ng1-webpack",
3 | "version": "1.0.0",
4 | "description": "A workflow for Angular made with Webpack",
5 | "scripts": {
6 | "build": "rimraf dist && webpack --env prod --bail --progress --profile",
7 | "server": "webpack-dev-server --env dev --history-api-fallback --inline --progress",
8 | "test": "karma start --env test",
9 | "test-watch": "karma start -env test --auto-watch --no-single-run",
10 | "start": "npm run server"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/Foxandxss/GermanWords-ng1-webpack.git"
15 | },
16 | "author": "Jesus Rodriguez",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/Foxandxss/GermanWords-ng1-webpack/issues"
20 | },
21 | "homepage": "https://github.com/Foxandxss/GermanWords-ng1-webpack",
22 | "dependencies": {
23 | "angular": "^1.5.0",
24 | "angular-ui-router": "^0.2.15",
25 | "bootstrap": "^3.3.4",
26 | "jwt-decode": "^1.1.1"
27 | },
28 | "devDependencies": {
29 | "angular-mocks": "^1.5.0",
30 | "autoprefixer": "^6.0.3",
31 | "babel-core": "^6.2.1",
32 | "babel-loader": "^6.2.0",
33 | "babel-preset-es2015": "^6.1.18",
34 | "copy-webpack-plugin": "^1.1.1",
35 | "css-loader": "^0.23.0",
36 | "extract-text-webpack-plugin": "^1.0.1",
37 | "file-loader": "^0.8.4",
38 | "html-webpack-plugin": "^2.7.1",
39 | "isparta-instrumenter-loader": "^1.0.0",
40 | "jasmine-core": "^2.3.4",
41 | "karma": "^0.13.14",
42 | "karma-coverage": "^0.5.3",
43 | "karma-jasmine": "^0.3.6",
44 | "karma-phantomjs2-launcher": "^0.5.0",
45 | "karma-sourcemap-loader": "^0.3.7",
46 | "karma-spec-reporter": "0.0.24",
47 | "karma-webpack": "^1.7.0",
48 | "node-libs-browser": "^1.0.0",
49 | "null-loader": "^0.1.1",
50 | "postcss-loader": "^0.8.0",
51 | "raw-loader": "^0.5.1",
52 | "rimraf": "^2.5.1",
53 | "style-loader": "^0.13.0",
54 | "webpack": "^2.0.7-beta",
55 | "webpack-dev-server": "^2.0.0-beta"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/app.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | routing.$inject = ['$urlRouterProvider', '$locationProvider'];
4 |
5 | export default function routing($urlRouterProvider, $locationProvider) {
6 | $locationProvider.html5Mode(true);
7 | $urlRouterProvider.otherwise('/');
8 | }
--------------------------------------------------------------------------------
/src/app/app.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.min.css';
2 |
3 | import angular from 'angular';
4 | import uirouter from 'angular-ui-router';
5 |
6 | import home from './features/home/index';
7 | import login from './features/login/index';
8 |
9 | import routing from './app.config';
10 |
11 | angular.module('app', [uirouter, home, login])
12 | .config(routing);
--------------------------------------------------------------------------------
/src/app/features/home/home.controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export default class HomeController {
4 | constructor(words, auth) {
5 | this.wordsSvc = words;
6 | this.authSvc = auth;
7 |
8 | this.isAuth = this.authSvc.isAuth();
9 |
10 | if (this.isAuth) {
11 | this.user = this.authSvc.getUser();
12 | }
13 | }
14 |
15 | getRandomWord() {
16 | this.wordsSvc.getWords().then((res) => {
17 | this.word = res.data;
18 | });
19 | }
20 |
21 | logout(event) {
22 | event.preventDefault();
23 | this.authSvc.logout();
24 | this.isAuth = false;
25 | this.user = null;
26 | }
27 | }
28 |
29 | HomeController.$inject = ['words', 'auth'];
--------------------------------------------------------------------------------
/src/app/features/home/home.controller.test.js:
--------------------------------------------------------------------------------
1 | import home from './index';
2 |
3 | describe('Controller: Home', function() {
4 | var $rootScope, $controller, $q, ctrl, auth, words;
5 |
6 | beforeEach(angular.mock.module(home));
7 |
8 | beforeEach(angular.mock.inject(function(_$controller_, _$q_, _$rootScope_, _auth_, _words_) {
9 | $rootScope = _$rootScope_;
10 | $q = _$q_;
11 | auth = _auth_;
12 | words = _words_;
13 | $controller = _$controller_;
14 | ctrl = $controller('HomeController', {words: words, auth: auth});
15 | }));
16 |
17 | it('isAuth is set on startup', function() {
18 | spyOn(auth, 'isAuth');
19 | $controller('HomeController', {words: words, auth: auth});
20 | expect(auth.isAuth).toHaveBeenCalled();
21 | });
22 |
23 | it('grabs the user info if the user is authenticated', function() {
24 | spyOn(auth, 'isAuth').and.returnValue(true);
25 | spyOn(auth, 'getUser');
26 | $controller('HomeController', {words: words, auth: auth});
27 | expect(auth.getUser).toHaveBeenCalled();
28 | });
29 |
30 | it('gets a random word with #getRandomWord', function() {
31 | var res = {
32 | data: {
33 | german: 'Haus'
34 | }
35 | };
36 | spyOn(words, 'getWords').and.returnValue($q.when(res));
37 |
38 | ctrl.getRandomWord();
39 |
40 | $rootScope.$digest();
41 |
42 | expect(ctrl.word).toEqual(res.data);
43 | });
44 |
45 | it('it resets everything on #logout', function() {
46 | spyOn(auth, 'logout');
47 |
48 | var event = {
49 | preventDefault: angular.noop
50 | };
51 |
52 | ctrl.logout(event);
53 |
54 | expect(ctrl.isAuth).toBeFalsy();
55 | expect(ctrl.user).toBeNull();
56 | expect(auth.logout).toHaveBeenCalled();
57 | });
58 | });
--------------------------------------------------------------------------------
/src/app/features/home/home.css:
--------------------------------------------------------------------------------
1 | img {
2 | width: 50px;
3 | }
--------------------------------------------------------------------------------
/src/app/features/home/home.html:
--------------------------------------------------------------------------------
1 |
2 |
German words demo!
3 |
Click the button below to get a random German word with its translation:
4 |
Give me a word!
5 |
6 |
Word: {{home.word.german}}
7 |
Translation: {{home.word.english}}
8 |
Please login below to see the translation
9 |
10 |
11 |
12 |
Welcome back {{home.user.username}}
13 |
Logout
14 |
15 |
--------------------------------------------------------------------------------
/src/app/features/home/home.routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | routes.$inject = ['$stateProvider'];
4 |
5 | export default function routes($stateProvider) {
6 | $stateProvider
7 | .state('home', {
8 | url: '/',
9 | template: require('./home.html'),
10 | controller: 'HomeController',
11 | controllerAs: 'home'
12 | });
13 | }
--------------------------------------------------------------------------------
/src/app/features/home/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import './home.css';
4 |
5 | import angular from 'angular';
6 | import uirouter from 'angular-ui-router';
7 |
8 | import routing from './home.routes';
9 | import HomeController from './home.controller';
10 | import wordsService from '../../services/words.service';
11 | import authService from '../../services/auth.service';
12 |
13 | export default angular.module('app.home', [uirouter, wordsService, authService])
14 | .config(routing)
15 | .controller('HomeController', HomeController)
16 | .name;
--------------------------------------------------------------------------------
/src/app/features/login/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import './login.css';
4 |
5 | import angular from 'angular';
6 | import uirouter from 'angular-ui-router';
7 |
8 | import routing from './login.routes';
9 | import LoginController from './login.controller';
10 | import authService from '../../services/auth.service';
11 |
12 | export default angular.module('app.login', [uirouter, authService])
13 | .config(routing)
14 | .controller('LoginController', LoginController)
15 | .name;
--------------------------------------------------------------------------------
/src/app/features/login/login.controller.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | export default class LoginController {
4 | constructor($state, auth) {
5 | this.$state = $state;
6 | this.authSvc = auth;
7 | }
8 |
9 | login() {
10 | this.authSvc.login(this.user.name, this.user.password).then(() => {
11 | this.$state.go('home');
12 | });
13 | }
14 | }
15 |
16 | LoginController.$inject = ['$state', 'auth'];
--------------------------------------------------------------------------------
/src/app/features/login/login.controller.test.js:
--------------------------------------------------------------------------------
1 | import login from './index';
2 |
3 | describe('Controller: Login', function() {
4 | var $rootScope, $controller, $q, $state, ctrl, auth;
5 |
6 | beforeEach(angular.mock.module(login));
7 |
8 | beforeEach(angular.mock.inject(function(_$controller_, _$q_, _$rootScope_, _$state_, _auth_) {
9 | $rootScope = _$rootScope_;
10 | $q = _$q_;
11 | $state = _$state_;
12 | auth = _auth_;
13 | $controller = _$controller_;
14 | ctrl = $controller('LoginController', {auth: auth});
15 | }));
16 |
17 | it('#login redirects home', function() {
18 | spyOn(auth, 'login').and.returnValue($q.when());
19 | spyOn($state, 'go');
20 |
21 | ctrl.user = {
22 | name: 'user',
23 | pass: 'name'
24 | };
25 |
26 | ctrl.login();
27 |
28 | $rootScope.$digest();
29 |
30 | expect($state.go).toHaveBeenCalledWith('home');
31 | });
32 | });
--------------------------------------------------------------------------------
/src/app/features/login/login.css:
--------------------------------------------------------------------------------
1 | .login {
2 | width: 40%;
3 | }
--------------------------------------------------------------------------------
/src/app/features/login/login.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/features/login/login.routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | routes.$inject = ['$stateProvider'];
4 |
5 | export default function routes($stateProvider) {
6 | $stateProvider
7 | .state('login', {
8 | url: '/login',
9 | template: require('./login.html'),
10 | controller: 'LoginController',
11 | controllerAs: 'login'
12 | });
13 | }
--------------------------------------------------------------------------------
/src/app/services/auth.service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import angular from 'angular';
4 | import uirouter from 'angular-ui-router';
5 |
6 | import jwtDecode from 'jwt-decode';
7 |
8 | class Auth {
9 | constructor($http) {
10 | this.$http = $http;
11 | this.token = localStorage.getItem('jwt');
12 | this.user = this.token && jwtDecode(this.token);
13 | }
14 |
15 | isAuth() {
16 | return !!this.token;
17 | }
18 |
19 | getUser() {
20 | return this.user;
21 | }
22 |
23 | login(username, password) {
24 | return this.$http.post('http://localhost:3001/sessions/create',
25 | JSON.stringify({username, password})
26 | ).then((res) => {
27 | this.token = res.data.id_token;
28 | localStorage.setItem('jwt', this.token);
29 | });
30 | }
31 |
32 | logout() {
33 | localStorage.removeItem('jwt');
34 | this.token = null;
35 | this.user = null;
36 | }
37 | }
38 |
39 | Auth.$inject = ['$http'];
40 |
41 | class AuthInterceptor {
42 | request(config) {
43 | const token = localStorage.getItem('jwt');
44 | if (token) {
45 | config.headers.Authorization = 'Bearer ' + token;
46 | }
47 | return config;
48 | }
49 | }
50 |
51 | config.$inject = ['$httpProvider'];
52 |
53 | function config($httpProvider) {
54 | $httpProvider.interceptors.push('authInterceptor');
55 | }
56 |
57 | export default angular.module('services.auth', [uirouter])
58 | .service('auth', Auth)
59 | .service('authInterceptor', AuthInterceptor)
60 | .config(config)
61 | .name;
--------------------------------------------------------------------------------
/src/app/services/auth.service.test.js:
--------------------------------------------------------------------------------
1 | import authModule from './auth.service';
2 |
3 | describe('Service: auth', function() {
4 | var $http, $q, $rootScope, auth;
5 |
6 | beforeEach(angular.mock.module(authModule));
7 |
8 | beforeEach(angular.mock.inject(function(_$http_, _$q_, _$rootScope_, _auth_) {
9 | $http = _$http_;
10 | $q = _$q_;
11 | $rootScope = _$rootScope_;
12 | auth = _auth_;
13 | }));
14 |
15 | it('#isAuth will return a boolean based on jwt token', function() {
16 | auth.token = 'foo';
17 |
18 | expect(auth.isAuth()).toBeTruthy();
19 |
20 | auth.token = null;
21 |
22 | expect(auth.isAuth()).toBeFalsy();
23 | });
24 |
25 | it('#getUser returns a decoded user', function() {
26 | auth.user = 'someuser';
27 |
28 | expect(auth.getUser()).toBe('someuser');
29 | });
30 |
31 | it('#login puts a new token in localStorage', function() {
32 | var res = {
33 | data: {
34 | id_token: 'foo'
35 | }
36 | };
37 |
38 | spyOn($http, 'post').and.returnValue($q.when(res));
39 | spyOn(localStorage, 'setItem');
40 |
41 | auth.login('user', 'pass');
42 |
43 | $rootScope.$digest();
44 |
45 | expect(localStorage.setItem).toHaveBeenCalledWith('jwt', 'foo');
46 | expect(auth.token).toBe('foo');
47 | });
48 |
49 | it('#logout reset everything', function() {
50 | spyOn(localStorage, 'removeItem');
51 |
52 | auth.logout();
53 |
54 | expect(localStorage.removeItem).toHaveBeenCalledWith('jwt');
55 | expect(auth.token).toBeNull();
56 | expect(auth.user).toBeNull();
57 | });
58 | });
--------------------------------------------------------------------------------
/src/app/services/words.service.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import angular from 'angular';
4 |
5 | class Words {
6 | constructor($http) {
7 | this.$http = $http;
8 | }
9 |
10 | getWords() {
11 | return this.$http.get('http://localhost:3001/api/random-word');
12 | }
13 | }
14 |
15 | Words.$inject = ['$http'];
16 |
17 | export default angular.module('services.words', [])
18 | .service('words', Words)
19 | .name;
--------------------------------------------------------------------------------
/src/app/services/words.service.test.js:
--------------------------------------------------------------------------------
1 | import wordsModule from './words.service';
2 |
3 | describe('Service: auth', function() {
4 | var $httpBackend, $q, $rootScope, words;
5 |
6 | beforeEach(angular.mock.module(wordsModule));
7 |
8 | beforeEach(angular.mock.inject(function(_$httpBackend_, _$q_, _$rootScope_, _words_) {
9 | $httpBackend = _$httpBackend_;
10 | $q = _$q_;
11 | $rootScope = _$rootScope_;
12 | words = _words_;
13 | }));
14 |
15 | afterEach(function() {
16 | $httpBackend.verifyNoOutstandingExpectation();
17 | $httpBackend.verifyNoOutstandingRequest();
18 | });
19 |
20 | it('gets a random word with #getWords()', function() {
21 | var res = {
22 | data: {
23 | german: 'Haus'
24 | }
25 | };
26 | $httpBackend.expectGET('http://localhost:3001/api/random-word').respond(200, res);
27 |
28 | words.getWords();
29 |
30 | $httpBackend.flush();
31 | });
32 | });
--------------------------------------------------------------------------------
/src/public/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Foxandxss/GermanWords-ng1-webpack/01c0a005ec85340a317e211a30644d73c4e90c45/src/public/img/favicon.ico
--------------------------------------------------------------------------------
/src/public/img/german-flag.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Foxandxss/GermanWords-ng1-webpack/01c0a005ec85340a317e211a30644d73c4e90c45/src/public/img/german-flag.jpg
--------------------------------------------------------------------------------
/src/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | German Words
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/tests.webpack.js:
--------------------------------------------------------------------------------
1 | // This file is an entry point for angular tests
2 | // Avoids some weird issues when using webpack + angular.
3 |
4 | import 'angular';
5 | import 'angular-mocks/angular-mocks';
6 |
7 | var testsContext = require.context(".", true, /.test$/);
8 | testsContext.keys().forEach(testsContext);
--------------------------------------------------------------------------------
/webpack.config.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 | var CopyWebpackPlugin = require('copy-webpack-plugin');
9 |
10 | module.exports = function makeWebpackConfig (ENV) {
11 | /**
12 | * Config
13 | * Reference: http://webpack.github.io/docs/configuration.html
14 | * This is the object where all configuration gets set
15 | */
16 | var config = {};
17 |
18 | /**
19 | * Entry
20 | * Reference: http://webpack.github.io/docs/configuration.html#entry
21 | * Should be an empty object if it's generating a test build
22 | * Karma will set this when it's a test build
23 | */
24 | config.entry = ENV === 'test' ? {} : {
25 | app: './src/app/app.js'
26 | };
27 |
28 | /**
29 | * Output
30 | * Reference: http://webpack.github.io/docs/configuration.html#output
31 | * Should be an empty object if it's generating a test build
32 | * Karma will handle setting it up for you when it's a test build
33 | */
34 | config.output = ENV === 'test' ? {} : {
35 | // Absolute output directory
36 | path: __dirname + '/dist',
37 |
38 | // Output path from the view of the page
39 | // Uses webpack-dev-server in development
40 | publicPath: ENV === 'prod' ? '/' : 'http://localhost:8080/',
41 |
42 | // Filename for entry points
43 | // Only adds hash in build mode
44 | filename: ENV === 'prod' ? '[name].[hash].js' : '[name].bundle.js',
45 |
46 | // Filename for non-entry points
47 | // Only adds hash in build mode
48 | chunkFilename: ENV === 'prod' ? '[name].[hash].js' : '[name].bundle.js'
49 | };
50 |
51 | /**
52 | * Devtool
53 | * Reference: http://webpack.github.io/docs/configuration.html#devtool
54 | * Type of sourcemap to use per build type
55 | */
56 | if (ENV === 'test') {
57 | config.devtool = 'inline-source-map';
58 | } else if (ENV === 'prod') {
59 | config.devtool = 'source-map';
60 | } else {
61 | config.devtool = 'eval-source-map';
62 | }
63 |
64 | /**
65 | * Loaders
66 | * Reference: http://webpack.github.io/docs/configuration.html#module-loaders
67 | * List: http://webpack.github.io/docs/list-of-loaders.html
68 | * This handles most of the magic responsible for converting modules
69 | */
70 |
71 | // Initialize module
72 | config.module = {
73 | preLoaders: [],
74 | loaders: [{
75 | // JS LOADER
76 | // Reference: https://github.com/babel/babel-loader
77 | // Transpile .js files using babel-loader
78 | // Compiles ES6 and ES7 into ES5 code
79 | test: /\.js$/,
80 | loader: 'babel',
81 | exclude: /node_modules/
82 | }, {
83 | // CSS LOADER
84 | // Reference: https://github.com/webpack/css-loader
85 | // Allow loading css through js
86 | //
87 | // Reference: https://github.com/postcss/postcss-loader
88 | // Postprocess your css with PostCSS plugins
89 | test: /\.css$/,
90 | // Reference: https://github.com/webpack/extract-text-webpack-plugin
91 | // Extract css files in production builds
92 | //
93 | // Reference: https://github.com/webpack/style-loader
94 | // Use style-loader in development.
95 | loader: ENV === 'test' ? 'null' : ExtractTextPlugin.extract('style', 'css?sourceMap!postcss')
96 | }, {
97 | // ASSET LOADER
98 | // Reference: https://github.com/webpack/file-loader
99 | // Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output
100 | // Rename the file using the asset hash
101 | // Pass along the updated reference to your code
102 | // You can add here any file extension you want to get copied to your output
103 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
104 | loader: 'file'
105 | }, {
106 | // HTML LOADER
107 | // Reference: https://github.com/webpack/raw-loader
108 | // Allow loading html through js
109 | test: /\.html$/,
110 | loader: 'raw'
111 | }]
112 | };
113 |
114 | // ISPARTA LOADER
115 | // Reference: https://github.com/ColCh/isparta-instrumenter-loader
116 | // Instrument JS files with Isparta for subsequent code coverage reporting
117 | // Skips node_modules and files that end with .test.js
118 | if (ENV === 'test') {
119 | config.module.preLoaders.push({
120 | test: /\.js$/,
121 | exclude: [
122 | /node_modules/,
123 | /\.spec\.js$/
124 | ],
125 | loader: 'isparta-instrumenter'
126 | })
127 | }
128 |
129 | /**
130 | * PostCSS
131 | * Reference: https://github.com/postcss/autoprefixer-core
132 | * Add vendor prefixes to your css
133 | */
134 | config.postcss = [
135 | autoprefixer({
136 | browsers: ['last 2 version']
137 | })
138 | ];
139 |
140 | /**
141 | * Plugins
142 | * Reference: http://webpack.github.io/docs/configuration.html#plugins
143 | * List: http://webpack.github.io/docs/list-of-plugins.html
144 | */
145 | config.plugins = [];
146 |
147 | // Skip rendering index.html in test mode
148 | if (ENV !== 'test') {
149 | // Reference: https://github.com/ampedandwired/html-webpack-plugin
150 | // Render index.html
151 | config.plugins.push(
152 | new HtmlWebpackPlugin({
153 | template: './src/public/index.html',
154 | inject: 'body'
155 | }),
156 |
157 | // Reference: https://github.com/webpack/extract-text-webpack-plugin
158 | // Extract css files
159 | // Disabled when in test mode or not in build mode
160 | new ExtractTextPlugin('[name].[hash].css', {disable: ENV !== 'prod'})
161 | )
162 | }
163 |
164 | // Add build specific plugins
165 | if (ENV === 'prod') {
166 | config.plugins.push(
167 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#noerrorsplugin
168 | // Only emit files when there are no errors
169 | new webpack.NoErrorsPlugin(),
170 |
171 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#dedupeplugin
172 | // Dedupe modules in the output
173 | new webpack.optimize.DedupePlugin(),
174 |
175 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
176 | // Minify all javascript, switch loaders to minimizing mode
177 | new webpack.optimize.UglifyJsPlugin(),
178 |
179 | // Copy assets from the public folder
180 | // Reference: https://github.com/kevlened/copy-webpack-plugin
181 | new CopyWebpackPlugin([{
182 | from: __dirname + '/src/public'
183 | }])
184 | )
185 | }
186 |
187 | /**
188 | * Dev server configuration
189 | * Reference: http://webpack.github.io/docs/configuration.html#devserver
190 | * Reference: http://webpack.github.io/docs/webpack-dev-server.html
191 | */
192 | config.devServer = {
193 | contentBase: './src/public',
194 | stats: {
195 | modules: false,
196 | cached: false,
197 | colors: true,
198 | chunk: false
199 | }
200 | };
201 |
202 | return config;
203 | };
204 |
--------------------------------------------------------------------------------