├── .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 |
--------------------------------------------------------------------------------