├── .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 |
16 | Login 17 |
-------------------------------------------------------------------------------- /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 |
2 |

Login

3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 | 13 |
14 |
-------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------