├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── karma.conf.js ├── package.json ├── src ├── app │ ├── app.js │ ├── app.utils.js │ └── components │ │ └── navbar │ │ ├── navbar.html │ │ ├── navbar.js │ │ └── navbar.spec.js ├── public │ ├── img │ │ ├── favicon.ico │ │ └── logo.png │ └── index.html ├── style │ └── app.scss └── tests.webpack.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js}] 14 | charset = utf-8 15 | 16 | # Indentation override for all JS under lib directory 17 | [src/**.js] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Matches the exact files either package.json or .travis.yml 22 | [{package.json,.travis.yml}] 23 | indent_style = space 24 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | 11 | "env": { 12 | "es6": true, 13 | "node": true 14 | }, 15 | 16 | "plugins": [ 17 | "standard", 18 | "promise" 19 | ], 20 | 21 | "globals": { 22 | "document": false, 23 | "navigator": false, 24 | "window": false 25 | }, 26 | 27 | "rules": { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | docs 5 | .idea 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | * Heavily commented webpack configuration with reasonable defaults. 4 | * ES6, and ES7 support with babel. 5 | * Source maps included in all builds. 6 | * Development server with live reload. 7 | * Production builds with cache busting. 8 | * Testing environment using karma to run tests and jasmine as the framework. 9 | * Code coverage when tests are run. 10 | * No gulp and no grunt, just npm scripts. 11 | 12 | >Warning: Make sure you're using the latest version of Node.js and NPM 13 | 14 | ### Quick start 15 | 16 | > Clone/Download the repo from git 17 | 18 | ```bash 19 | # clone repo 20 | $ git clone https://github.com/alamgird/angular-starter.git my-app 21 | 22 | # change directory to your app 23 | $ cd my-app 24 | 25 | # install the dependencies with npm 26 | $ npm install 27 | 28 | # start the server 29 | $ npm start 30 | ``` 31 | 32 | Go to [http://localhost:8080](http://localhost:8080) in your browser. 33 | 34 | # Table of Contents 35 | 36 | * [Getting Started](#getting-started) 37 | * [Dependencies](#dependencies) 38 | * [Installing](#installing) 39 | * [Running the app](#running-the-app) 40 | * [Developing](#developing) 41 | * [Testing](#testing) 42 | 43 | # Getting Started 44 | 45 | ## Dependencies 46 | 47 | What you need to run this app: 48 | * `node` and `npm` (Use [NVM](https://github.com/creationix/nvm)) 49 | * Ensure you're running Node (`v4.1.x`+) and NPM (`2.14.x`+) 50 | 51 | ## Installing 52 | 53 | * `clone` this repo 54 | * `npm install` to install all dependencies 55 | 56 | ## Running the app 57 | 58 | After you have installed all dependencies you can now run the app with: 59 | ```bash 60 | npm start 61 | ``` 62 | 63 | It will start a local server using `webpack-dev-server` which will watch, build (in-memory), and reload for you. The port will be displayed to you as `http://localhost:8080`. 64 | 65 | ## Developing 66 | 67 | ### Build files 68 | 69 | * single run: `npm run build` 70 | * build files and watch: `npm run watch` 71 | 72 | ## Testing 73 | 74 | #### 1. Unit Tests 75 | 76 | * single run: `npm test` 77 | * live mode (TDD style): `npm run test-watch` 78 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function karmaConfig(config) { 2 | config.set({ 3 | frameworks: [ 4 | 'jasmine' 5 | ], 6 | 7 | reporters: [ 8 | 'progress', 'coverage' 9 | ], 10 | 11 | files: [ 12 | 'src/tests.webpack.js' 13 | ], 14 | 15 | preprocessors: { 16 | 'src/tests.webpack.js': ['webpack', 'sourcemap'] 17 | }, 18 | 19 | browsers: [ 20 | 'PhantomJS' 21 | ], 22 | 23 | singleRun: true, 24 | 25 | coverageReporter: { 26 | dir: 'coverage/', 27 | reporters: [ 28 | {type: 'text-summary'}, 29 | {type: 'html'} 30 | ] 31 | }, 32 | 33 | webpack: require('./webpack.config'), 34 | 35 | webpackMiddleware: { 36 | noInfo: 'errors-only' 37 | } 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-starter", 3 | "version": "1.0.0", 4 | "description": "Angular 1.5 starter project with unit tests, code coverage and component based design", 5 | "scripts": { 6 | "clean": "rimraf dist && rimraf docs && rimraf coverage", 7 | "build": "rimraf dist && webpack --bail --progress --profile", 8 | "build:run": "npm run build && live-server --port=3001 --open=dist/", 9 | "server": "webpack-dev-server --history-api-fallback --inline --progress", 10 | "coverage": "live-server --port=3001 --open=coverage/", 11 | "test": "karma start", 12 | "test:coverage": "npm run test && npm run coverage", 13 | "test:watch": "karma start --auto-watch --no-single-run", 14 | "docs": "jsdoc src/app -r -d docs", 15 | "docs:launch": "npm run docs && live-server --port=3002 --open=docs/", 16 | "start": "npm run server" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/Foxandxss/angular-webpack-workflow.git" 21 | }, 22 | "author": "Daiyan Alamgir", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/Foxandxss/angular-webpack-workflow/issues" 26 | }, 27 | "homepage": "https://github.com/Foxandxss/angular-webpack-workflow", 28 | "dependencies": { 29 | "@angular/router": "^0.2.0", 30 | "angular": "^1.5.0", 31 | "angular-sanitize": "^1.5.3", 32 | "angular-ui-select": "^0.12.100", 33 | "bootstrap": "^3.3.6", 34 | "font-awesome": "^4.6.1", 35 | "paper-css": "^0.1.2" 36 | }, 37 | "devDependencies": { 38 | "angular-mocks": "^1.5.0", 39 | "autoprefixer": "^6.0.3", 40 | "babel-core": "^6.2.1", 41 | "babel-loader": "^6.2.0", 42 | "babel-preset-es2015": "^6.1.18", 43 | "copy-webpack-plugin": "^1.1.1", 44 | "css-loader": "^0.23.0", 45 | "eslint": "^2.8.0", 46 | "eslint-loader": "^1.3.0", 47 | "eslint-plugin-promise": "^1.1.0", 48 | "eslint-plugin-standard": "^1.3.2", 49 | "extract-text-webpack-plugin": "^1.0.1", 50 | "file-loader": "^0.8.4", 51 | "html-webpack-plugin": "^2.7.1", 52 | "isparta-instrumenter-loader": "^1.0.0", 53 | "jasmine-core": "^2.3.4", 54 | "jsdoc": "^3.4.0", 55 | "karma": "^0.13.14", 56 | "karma-coverage": "^0.5.3", 57 | "karma-jasmine": "^0.3.6", 58 | "karma-phantomjs-launcher": "^1.0.0", 59 | "karma-sourcemap-loader": "^0.3.7", 60 | "karma-spec-reporter": "0.0.26", 61 | "karma-webpack": "^1.7.0", 62 | "live-server": "^0.9.2", 63 | "ng-annotate-webpack-plugin": "^0.1.2", 64 | "node-libs-browser": "^1.0.0", 65 | "node-sass": "^3.4.2", 66 | "null-loader": "^0.1.1", 67 | "phantomjs-prebuilt": "^2.1.4", 68 | "postcss-loader": "^0.8.0", 69 | "raw-loader": "^0.5.1", 70 | "rimraf": "^2.5.1", 71 | "sass-loader": "^3.2.0", 72 | "style-loader": "^0.13.0", 73 | "url-loader": "^0.5.7", 74 | "webpack": "^1.12.13", 75 | "webpack-dev-server": "^1.14.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | import ComponentRouter from '@angular/router/angular1/angular_1_router'; 3 | import UISelect from 'angular-ui-select/select.min'; 4 | import ngSanitize from 'angular-sanitize'; 5 | import Navbar from 'components/navbar/navbar'; 6 | import ServiceFactory from './app.utils'; 7 | 8 | import '../style/app.scss'; 9 | import 'angular-ui-select/select.min.css'; 10 | 11 | let AppComponent = { 12 | template: ` 13 |
14 | 15 |
16 | `, 17 | controller: () => new AppController() 18 | }; 19 | 20 | class AppController { } 21 | 22 | angular 23 | .module('synopsis', ['ngComponentRouter', 'ui.select', 'ngSanitize']) 24 | .config(($locationProvider) => $locationProvider.html5Mode(true)) 25 | .value('$routerRootComponent', 'app') 26 | .component('app', AppComponent) 27 | .component('navbar', Navbar); 28 | 29 | angular 30 | .element(document) 31 | .ready(() => angular.bootstrap(document, ['synopsis'])); 32 | 33 | export default AppController; 34 | -------------------------------------------------------------------------------- /src/app/app.utils.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | class ServiceFactory { 4 | 5 | constructor() { 6 | this.injector = angular.injector(['ng']); 7 | } 8 | 9 | getService(name) { 10 | return this.injector.get(name); 11 | } 12 | 13 | } 14 | 15 | export default new ServiceFactory(); 16 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.js: -------------------------------------------------------------------------------- 1 | class NavBarController { 2 | 3 | constructor() { 4 | 5 | this.brand = 'Synopsis'; 6 | 7 | this.items = [{ 8 | href: '#', 9 | label: 'Home', 10 | isActive: true 11 | }, { 12 | href: '#', 13 | label: 'About', 14 | isActive: false 15 | }, { 16 | href: '#', 17 | label: 'Contact', 18 | isActive: false 19 | }]; 20 | 21 | } 22 | 23 | onItemClicked(clickedItem) { 24 | this.items = this.items.map((item) => { 25 | item.isActive = item.label === clickedItem.label; 26 | return item; 27 | }); 28 | } 29 | } 30 | 31 | const Navbar = { 32 | template: require('./navbar.html'), 33 | controller: () => new NavBarController() 34 | }; 35 | 36 | export default Navbar; 37 | -------------------------------------------------------------------------------- /src/app/components/navbar/navbar.spec.js: -------------------------------------------------------------------------------- 1 | import NavBar from './navbar'; 2 | 3 | describe('Navbar Component Tests', () => { 4 | 5 | let controller; 6 | 7 | beforeEach(() => { 8 | controller = NavBar.controller(); 9 | }); 10 | 11 | it('should have the Synopsis brand', () => { 12 | expect(controller.brand).toEqual('Synopsis'); 13 | }); 14 | 15 | it('should have 3 menu items', () => { 16 | expect(controller.items.length).toEqual(3); 17 | }); 18 | 19 | it('should select the clicked item', () => { 20 | 21 | var itemToClick = controller.items[2]; 22 | expect(itemToClick.isActive).toEqual(false); 23 | 24 | controller.onItemClicked(itemToClick); 25 | expect(itemToClick.isActive).toEqual(true); 26 | 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalamgir/angular-starter/30393902d3d95b1b1cde856517e5f710b1148252/src/public/img/favicon.ico -------------------------------------------------------------------------------- /src/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dalamgir/angular-starter/30393902d3d95b1b1cde856517e5f710b1148252/src/public/img/logo.png -------------------------------------------------------------------------------- /src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular App 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/style/app.scss: -------------------------------------------------------------------------------- 1 | @import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; 2 | @import '../../node_modules/font-awesome/css/font-awesome.min.css'; 3 | 4 | * { 5 | border-radius: 0px !important; 6 | } 7 | 8 | .container-fluid { 9 | padding: 0px; 10 | } 11 | 12 | .box { 13 | width: 70%; 14 | height: 200px; 15 | background: #FFF; 16 | margin: 40px auto; 17 | } 18 | 19 | .sheet { 20 | box-shadow: 0 10px 6px -6px #777; 21 | } 22 | -------------------------------------------------------------------------------- /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, /.spec$/); 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 | var ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 10 | var path = require('path'); 11 | 12 | /** 13 | * Env 14 | * Get npm lifecycle event to identify the environment 15 | * Example: When the `npm run build` command is executed, the ENV will be set to `build` 16 | */ 17 | var ENV = process.env.npm_lifecycle_event; 18 | var isTest = ENV === 'test' || ENV === 'test-watch'; 19 | var isProd = ENV === 'build'; 20 | 21 | module.exports = function makeWebpackConfig() { 22 | /** 23 | * Config 24 | * Reference: http://webpack.github.io/docs/configuration.html 25 | * This is the object where all configuration gets set 26 | */ 27 | var config = {}; 28 | 29 | /** 30 | * Entry 31 | * Reference: http://webpack.github.io/docs/configuration.html#entry 32 | * Should be an empty object if it's generating a test build 33 | * Karma will set this when it's a test build 34 | */ 35 | config.entry = isTest ? {} : { 36 | app: './src/app/app.js' 37 | }; 38 | 39 | /** 40 | * Output 41 | * Reference: http://webpack.github.io/docs/configuration.html#output 42 | * Should be an empty object if it's generating a test build 43 | * Karma will handle setting it up for you when it's a test build 44 | */ 45 | config.output = isTest ? {} : { 46 | // Absolute output directory 47 | path: __dirname + '/dist', 48 | 49 | // Output path from the view of the page 50 | // Uses webpack-dev-server in development 51 | publicPath: isProd ? '/' : 'http://localhost:8080/', 52 | 53 | // Filename for entry points 54 | // Only adds hash in build mode 55 | filename: isProd ? '[name].[hash].js' : '[name].bundle.js', 56 | 57 | // Filename for non-entry points 58 | // Only adds hash in build mode 59 | chunkFilename: isProd ? '[name].[hash].js' : '[name].bundle.js' 60 | }; 61 | 62 | /** 63 | * Devtool 64 | * Reference: http://webpack.github.io/docs/configuration.html#devtool 65 | * Type of sourcemap to use per build type 66 | */ 67 | if (isTest) { 68 | config.devtool = 'inline-source-map'; 69 | } else if (isProd) { 70 | config.devtool = 'source-map'; 71 | } else { 72 | config.devtool = 'eval-source-map'; 73 | } 74 | 75 | /** 76 | * Loaders 77 | * Reference: http://webpack.github.io/docs/configuration.html#module-loaders 78 | * List: http://webpack.github.io/docs/list-of-loaders.html 79 | * This handles most of the magic responsible for converting modules 80 | */ 81 | 82 | // Initialize module 83 | config.module = { 84 | 85 | preLoaders: [{ 86 | test: /\.js$/, 87 | loader: "eslint-loader?{rules:{semi:0}}", 88 | exclude: /node_modules/ 89 | }], 90 | 91 | loaders: [{ 92 | // JS LOADER 93 | // Reference: https://github.com/babel/babel-loader 94 | // Transpile .js files using babel-loader 95 | // Compiles ES6 and ES7 into ES5 code 96 | test: /\.js$/, 97 | loader: 'babel', 98 | exclude: /node_modules/ 99 | }, { 100 | // CSS LOADER 101 | // Reference: https://github.com/webpack/css-loader 102 | // Allow loading css through js 103 | // 104 | // Reference: https://github.com/postcss/postcss-loader 105 | // Postprocess your css with PostCSS plugins 106 | test: /\.scss|.css$/, 107 | // Reference: https://github.com/webpack/extract-text-webpack-plugin 108 | // Extract css files in production builds 109 | // 110 | // Reference: https://github.com/webpack/style-loader 111 | // Use style-loader in development. 112 | loader: isTest ? 'null' : ExtractTextPlugin.extract('style', 'css?sourceMap!sass!postcss') 113 | }, { 114 | // ASSET LOADER 115 | // Reference: https://github.com/webpack/file-loader 116 | // Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output 117 | // Rename the file using the asset hash 118 | // Pass along the updated reference to your code 119 | // You can add here any file extension you want to get copied to your output 120 | test: /\.(png|jpg|jpeg|gif)$/, 121 | loader: 'file' 122 | },{ 123 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 124 | loader: "url?limit=10000&mimetype=application/font-woff" 125 | }, { 126 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 127 | loader: "url?limit=10000&mimetype=application/font-woff" 128 | }, { 129 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 130 | loader: "url?limit=10000&mimetype=application/octet-stream" 131 | }, { 132 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 133 | loader: "file" 134 | }, { 135 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 136 | loader: "url?limit=10000&mimetype=image/svg+xml" 137 | }, { 138 | // HTML LOADER 139 | // Reference: https://github.com/webpack/raw-loader 140 | // Allow loading html through js 141 | test: /\.html$/, 142 | loader: 'raw' 143 | }] 144 | }; 145 | 146 | // ISPARTA LOADER 147 | // Reference: https://github.com/ColCh/isparta-instrumenter-loader 148 | // Instrument JS files with Isparta for subsequent code coverage reporting 149 | // Skips node_modules and files that end with .test.js 150 | if (isTest) { 151 | config.module.preLoaders.push({ 152 | test: /\.js$/, 153 | exclude: [ 154 | /node_modules/, 155 | /\.spec\.js$/ 156 | ], 157 | loader: 'isparta-instrumenter' 158 | }); 159 | } 160 | 161 | /** 162 | * PostCSS 163 | * Reference: https://github.com/postcss/autoprefixer-core 164 | * Add vendor prefixes to your css 165 | */ 166 | config.postcss = [ 167 | autoprefixer({ 168 | browsers: ['last 2 version'] 169 | }) 170 | ]; 171 | 172 | /** 173 | * Plugins 174 | * Reference: http://webpack.github.io/docs/configuration.html#plugins 175 | * List: http://webpack.github.io/docs/list-of-plugins.html 176 | */ 177 | config.plugins = [ 178 | new ngAnnotatePlugin({ 179 | add: true 180 | }) 181 | ]; 182 | 183 | // Skip rendering index.html in test mode 184 | if (!isTest) { 185 | // Reference: https://github.com/ampedandwired/html-webpack-plugin 186 | // Render index.html 187 | config.plugins.push( 188 | new HtmlWebpackPlugin({ 189 | template: './src/public/index.html', 190 | inject: 'body' 191 | }), 192 | 193 | // Reference: https://github.com/webpack/extract-text-webpack-plugin 194 | // Extract css files 195 | // Disabled when in test mode or not in build mode 196 | new ExtractTextPlugin('[name].[hash].css', {disable: !isProd}) 197 | ) 198 | } 199 | 200 | // Add build specific plugins 201 | if (isProd) { 202 | config.plugins.push( 203 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#noerrorsplugin 204 | // Only emit files when there are no errors 205 | new webpack.NoErrorsPlugin(), 206 | 207 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#dedupeplugin 208 | // Dedupe modules in the output 209 | new webpack.optimize.DedupePlugin(), 210 | 211 | // Reference: http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin 212 | // Minify all javascript, switch loaders to minimizing mode 213 | //new webpack.optimize.UglifyJsPlugin(), 214 | 215 | // Copy assets from the public folder 216 | // Reference: https://github.com/kevlened/copy-webpack-plugin 217 | new CopyWebpackPlugin([{ 218 | from: __dirname + '/src/public' 219 | }]) 220 | ) 221 | } 222 | 223 | config.resolve = { 224 | root: [path.resolve(__dirname, 'src/app')], 225 | extensions: ['', '.js'] 226 | }; 227 | 228 | /** 229 | * Dev server configuration 230 | * Reference: http://webpack.github.io/docs/configuration.html#devserver 231 | * Reference: http://webpack.github.io/docs/webpack-dev-server.html 232 | */ 233 | config.devServer = { 234 | contentBase: './src/public', 235 | stats: 'minimal' 236 | }; 237 | 238 | return config; 239 | }(); 240 | --------------------------------------------------------------------------------