├── .gitignore ├── .nvmrc ├── karma.conf.js ├── package.json ├── readme.md ├── src ├── app │ ├── components │ │ ├── app │ │ │ ├── app.component.js │ │ │ └── app.template.html │ │ ├── index.js │ │ ├── todo-footer │ │ │ ├── todo-footer.component.js │ │ │ └── todo-footer.template.html │ │ ├── todo-header │ │ │ ├── todo-header.component.js │ │ │ └── todo-header.template.html │ │ ├── todo-item │ │ │ ├── todo-item.component.js │ │ │ └── todo-item.template.html │ │ └── todo-list │ │ │ ├── todo-list.component.js │ │ │ └── todo-list.template.html │ ├── main.js │ ├── main.module.js │ ├── models │ │ └── todo.model.js │ ├── pipes │ │ ├── index.js │ │ └── trim │ │ │ └── trim.pipe.js │ ├── polyfill.js │ ├── routes.js │ ├── services │ │ ├── todo-store.service.js │ │ └── todo-store.service.spec.js │ ├── style.js │ ├── test.js │ └── vendor.js ├── favicon.ico └── index.html ├── webpack.common.config.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .idea 3 | node_modules 4 | npm-debug.log -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 6.5.0 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | 4 | let npmLifecycleEvent = process.env.npm_lifecycle_event || ''; 5 | let isSingleRun = npmLifecycleEvent === 'test'; 6 | 7 | module.exports = function(config) { 8 | config.set({ 9 | basePath: '', 10 | 11 | frameworks: ['jasmine'], 12 | 13 | files: [ 14 | { pattern: 'src/app/test.js' } 15 | ], 16 | 17 | preprocessors: { 18 | 'src/app/test.js': ['webpack', 'sourcemap'] 19 | }, 20 | 21 | reporters: ['dots'], 22 | 23 | webpack: require('./webpack.config'), 24 | 25 | webpackServer: { 26 | stats: 'errors-only', 27 | noInfo: true 28 | }, 29 | 30 | port: 9876, 31 | 32 | colors: true, 33 | 34 | logLevel: config.LOG_INFO, 35 | 36 | browsers: ['PhantomJS'], 37 | 38 | autoWatch: !isSingleRun, 39 | singleRun: isSingleRun 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-esnext-todomvc", 3 | "version": "3.0.0", 4 | "main": "client/app/main.js", 5 | "scripts": { 6 | "build": "webpack", 7 | "deploy": "gh-pages -d dist", 8 | "start": "webpack-dev-server", 9 | "test": "karma start", 10 | "test-watch": "karma start" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/blacksonic/angular2-esnext-todomvc.git" 15 | }, 16 | "author": { 17 | "name": "blacksonic", 18 | "email": "soos.gabor86@gmail.com" 19 | }, 20 | "license": "ISC", 21 | "keywords": [ 22 | "angular2", 23 | "todomvc", 24 | "es6" 25 | ], 26 | "description": "Angular TodoMVC application in Javascript (ES6/ES7).", 27 | "engines": { 28 | "node": "6.5.0" 29 | }, 30 | "dependencies": { 31 | "@angular/common": "~4.1.2", 32 | "@angular/compiler": "~4.1.2", 33 | "@angular/core": "~4.1.2", 34 | "@angular/forms": "~4.1.2", 35 | "@angular/http": "~4.1.2", 36 | "@angular/platform-browser": "~4.1.2", 37 | "@angular/platform-browser-dynamic": "~4.1.2", 38 | "@angular/router": "~4.1.2", 39 | "core-js": "~2.4.1", 40 | "rxjs": "~5.4.0", 41 | "todomvc-app-css": "~2.1.0", 42 | "todomvc-common": "~1.0.3", 43 | "uuid": "~3.0.1", 44 | "zone.js": "~0.8.5" 45 | }, 46 | "devDependencies": { 47 | "babel-core": "~6.24.0", 48 | "babel-loader": "~7.0.0", 49 | "babel-preset-angular2": "~0.0.2", 50 | "babel-preset-env": "~1.4.0", 51 | "copy-webpack-plugin": "~4.0.1", 52 | "css-loader": "~0.28.0", 53 | "extract-text-webpack-plugin": "~2.1.0", 54 | "gh-pages": "~1.0.0", 55 | "html-loader": "~0.4.5", 56 | "jasmine-core": "~2.6.1", 57 | "karma": "~1.7.0", 58 | "karma-jasmine": "~1.1.0", 59 | "karma-phantomjs-launcher": "~1.0.4", 60 | "karma-sourcemap-loader": "~0.3.7", 61 | "karma-webpack": "~2.0.3", 62 | "source-map-loader": "~0.2.1", 63 | "style-loader": "~0.17.0", 64 | "webpack": "~2.5.1", 65 | "webpack-dev-server": "~2.4.2", 66 | "webpack-merge": "~4.1.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Angular ESNext TodoMVC 2 | [![Dependency Status](https://david-dm.org/blacksonic/angular2-esnext-todomvc.svg)](https://david-dm.org/blacksonic/angular2-esnext-todomvc) 3 | [![devDependency Status](https://david-dm.org/blacksonic/angular2-esnext-todomvc/dev-status.svg)](https://david-dm.org/blacksonic/angular2-esnext-todomvc?type=dev) 4 | 5 | TodoMVC application built with Angular in Javascript (ES6/ES7). 6 | 7 | Layout and namings based on the [official Angular style guide](https://angular.io/styleguide). 8 | 9 | ### Concepts it covers 10 | 11 | - Creating @Component with directives (ngIf, ngFor) 12 | - Creating child @Component and communicating with parent (@Input, @Output) 13 | - Using Dependency Injection for service 14 | - Use @Pipe inside @Component 15 | - Routing 16 | 17 | ### Quick Start 18 | 19 | ```bash 20 | 21 | git clone https://github.com/blacksonic/angular2-esnext-todomvc.git 22 | cd angular2-esnext-todomvc 23 | 24 | npm install 25 | npm start 26 | 27 | ``` 28 | 29 | Open it in your browser [http://localhost:3000](http://localhost:3000). 30 | -------------------------------------------------------------------------------- /src/app/components/app/app.component.js: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | 3 | import template from './app.template.html'; 4 | 5 | @Component({ 6 | selector: 'todo-app', 7 | template: template 8 | }) 9 | export class AppComponent { 10 | constructor(@Inject('AUTHOR') author) { 11 | this.author = author; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/components/app/app.template.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /src/app/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './app/app.component'; 2 | export * from './todo-footer/todo-footer.component'; 3 | export * from './todo-header/todo-header.component'; 4 | export * from './todo-item/todo-item.component'; 5 | export * from './todo-list/todo-list.component'; 6 | -------------------------------------------------------------------------------- /src/app/components/todo-footer/todo-footer.component.js: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { TodoStoreService } from '../../services/todo-store.service'; 5 | import template from './todo-footer.template.html'; 6 | 7 | @Component({ 8 | selector: 'todo-footer', 9 | template: template 10 | }) 11 | export class TodoFooterComponent { 12 | constructor(todoStore: TodoStoreService, route: ActivatedRoute) { 13 | this._todoStore = todoStore; 14 | this._route = route; 15 | this.currentStatus = ''; 16 | } 17 | 18 | ngOnInit() { 19 | this._route.params 20 | .map(params => params.status) 21 | .subscribe((status) => { 22 | this.currentStatus = status || ''; 23 | }); 24 | } 25 | 26 | removeCompleted() { 27 | this._todoStore.removeCompleted(); 28 | } 29 | 30 | getCount() { 31 | return this._todoStore.todos.length; 32 | } 33 | 34 | getRemainingCount() { 35 | return this._todoStore.getRemaining().length; 36 | } 37 | 38 | hasCompleted() { 39 | return this._todoStore.getCompleted().length > 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/components/todo-footer/todo-footer.template.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/app/components/todo-header/todo-header.component.js: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { TodoStoreService } from '../../services/todo-store.service'; 4 | import template from './todo-header.template.html'; 5 | 6 | @Component({ 7 | selector: 'todo-header', 8 | template: template 9 | }) 10 | export class TodoHeaderComponent { 11 | newTodo = ''; 12 | 13 | constructor(todoStore: TodoStoreService) { 14 | this._todoStore = todoStore; 15 | } 16 | 17 | addTodo() { 18 | if (this.newTodo.trim().length) { 19 | this._todoStore.add(this.newTodo); 20 | this.newTodo = ''; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/components/todo-header/todo-header.template.html: -------------------------------------------------------------------------------- 1 |
2 |

Todos

3 | 4 |
5 | -------------------------------------------------------------------------------- /src/app/components/todo-item/todo-item.component.js: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output, Input } from '@angular/core'; 2 | 3 | import template from './todo-item.template.html'; 4 | 5 | @Component({ 6 | selector: 'todo-item', 7 | template: template 8 | }) 9 | export class TodoItemComponent { 10 | @Input() todo; 11 | 12 | @Output() itemModified = new EventEmitter(); 13 | 14 | @Output() itemRemoved = new EventEmitter(); 15 | 16 | editing = false; 17 | 18 | cancelEditing() { 19 | this.editing = false; 20 | } 21 | 22 | stopEditing(editedTitle) { 23 | this.todo.setTitle(editedTitle.value); 24 | this.editing = false; 25 | 26 | if (this.todo.title.length === 0) { 27 | this.remove(); 28 | } else { 29 | this.update(); 30 | } 31 | } 32 | 33 | edit() { 34 | this.editing = true; 35 | } 36 | 37 | toggleCompletion() { 38 | this.todo.completed = !this.todo.completed; 39 | this.update(); 40 | } 41 | 42 | remove() { 43 | this.itemRemoved.next(this.todo.uid); 44 | } 45 | 46 | update() { 47 | this.itemModified.next(this.todo.uid); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/components/todo-item/todo-item.template.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 | 4 | 5 | 6 |
    7 | 8 |
  • 9 | -------------------------------------------------------------------------------- /src/app/components/todo-list/todo-list.component.js: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | import { TodoStoreService } from '../../services/todo-store.service'; 5 | import template from './todo-list.template.html'; 6 | 7 | @Component({ 8 | selector: 'todo-list', 9 | template: template 10 | }) 11 | export class TodoListComponent { 12 | constructor(todoStore: TodoStoreService, route: ActivatedRoute) { 13 | this._todoStore = todoStore; 14 | this._route = route; 15 | this._currentStatus = ''; 16 | } 17 | 18 | ngOnInit() { 19 | this._route.params 20 | .map(params => params.status) 21 | .subscribe((status) => { 22 | this._currentStatus = status; 23 | }); 24 | } 25 | 26 | remove(uid) { 27 | this._todoStore.remove(uid); 28 | } 29 | 30 | update() { 31 | this._todoStore.persist(); 32 | } 33 | 34 | getTodos() { 35 | if (this._currentStatus == 'completed') { 36 | return this._todoStore.getCompleted(); 37 | } 38 | else if (this._currentStatus == 'active') { 39 | return this._todoStore.getRemaining(); 40 | } 41 | else { 42 | return this._todoStore.todos; 43 | } 44 | } 45 | 46 | allCompleted() { 47 | return this._todoStore.allCompleted(); 48 | } 49 | 50 | setAllTo(toggleAll) { 51 | this._todoStore.setAllTo(toggleAll.checked); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/components/todo-list/todo-list.template.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 | 8 |
    9 | 10 |
    -------------------------------------------------------------------------------- /src/app/main.js: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { MainModule } from './main.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(MainModule); 6 | -------------------------------------------------------------------------------- /src/app/main.module.js: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { HttpModule } from '@angular/http'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NgModule } from '@angular/core'; 6 | 7 | import { TodoStoreService } from './services/todo-store.service'; 8 | import { 9 | AppComponent, 10 | TodoListComponent, 11 | TodoFooterComponent, 12 | TodoHeaderComponent, 13 | TodoItemComponent 14 | } from './components'; 15 | import { TrimPipe } from './pipes'; 16 | import { routes } from './routes'; 17 | 18 | @NgModule({ 19 | bootstrap: [AppComponent], 20 | declarations: [ 21 | AppComponent, 22 | TodoListComponent, 23 | TodoFooterComponent, 24 | TodoHeaderComponent, 25 | TodoItemComponent, 26 | TrimPipe 27 | ], 28 | imports: [ 29 | BrowserModule, 30 | FormsModule, 31 | HttpModule, 32 | RouterModule.forRoot(routes, { 33 | useHash: true 34 | }) 35 | ], 36 | providers: [ 37 | TodoStoreService, 38 | { provide: 'AUTHOR', useValue: 'Soós Gábor' } 39 | ] 40 | }) 41 | export class MainModule {} 42 | -------------------------------------------------------------------------------- /src/app/models/todo.model.js: -------------------------------------------------------------------------------- 1 | import * as uuid from 'uuid'; 2 | 3 | export class TodoModel { 4 | completed; 5 | title; 6 | uid; 7 | 8 | setTitle(title) { 9 | this.title = title.trim(); 10 | } 11 | 12 | constructor(title) { 13 | this.uid = uuid.v4(); 14 | this.completed = false; 15 | this.title = title.trim(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/pipes/index.js: -------------------------------------------------------------------------------- 1 | export * from './trim/trim.pipe'; 2 | -------------------------------------------------------------------------------- /src/app/pipes/trim/trim.pipe.js: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'trim' }) 4 | export class TrimPipe implements PipeTransform { 5 | transform(value, args) { 6 | return value.trim(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/polyfill.js: -------------------------------------------------------------------------------- 1 | import 'core-js/client/shim'; 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/long-stack-trace-zone'; 5 | -------------------------------------------------------------------------------- /src/app/routes.js: -------------------------------------------------------------------------------- 1 | import { TodoListComponent } from './components/todo-list/todo-list.component'; 2 | 3 | export let routes = [ 4 | { path: '', component: TodoListComponent, pathMatch: 'full' }, 5 | { path: ':status', component: TodoListComponent } 6 | ]; 7 | -------------------------------------------------------------------------------- /src/app/services/todo-store.service.js: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { TodoModel } from '../models/todo.model'; 4 | 5 | @Injectable() 6 | export class TodoStoreService { 7 | todos = []; 8 | 9 | constructor() { 10 | let persistedTodos = JSON.parse(localStorage.getItem('angular2-todos')) || []; 11 | 12 | this.todos = persistedTodos.map( (todo) => { 13 | let ret = new TodoModel(todo.title); 14 | ret.completed = todo.completed; 15 | ret.uid = todo.uid; 16 | return ret; 17 | }); 18 | } 19 | 20 | get(state) { 21 | return this.todos.filter((todo) => todo.completed === state.completed); 22 | } 23 | 24 | allCompleted() { 25 | return this.todos.length === this.getCompleted().length; 26 | } 27 | 28 | setAllTo(completed) { 29 | this.todos.forEach((todo) => todo.completed = completed); 30 | this.persist(); 31 | } 32 | 33 | removeCompleted() { 34 | this.todos = this.get({completed: false}); 35 | this.persist(); 36 | } 37 | 38 | getRemaining() { 39 | if (!this.remainingTodos) { 40 | this.remainingTodos = this.get({completed: false}); 41 | } 42 | 43 | return this.remainingTodos; 44 | } 45 | 46 | getCompleted() { 47 | if (!this.completedTodos) { 48 | this.completedTodos = this.get({completed: true}); 49 | } 50 | 51 | return this.completedTodos; 52 | } 53 | 54 | toggleCompletion(uid) { 55 | let todo = this._findByUid(uid); 56 | 57 | if (todo) { 58 | todo.completed = !todo.completed; 59 | this.persist(); 60 | } 61 | } 62 | 63 | remove(uid) { 64 | let todo = this._findByUid(uid); 65 | 66 | if (todo) { 67 | this.todos.splice(this.todos.indexOf(todo), 1); 68 | this.persist(); 69 | } 70 | } 71 | 72 | add(title) { 73 | this.todos.push(new TodoModel(title)); 74 | this.persist(); 75 | } 76 | 77 | persist() { 78 | this._clearCache(); 79 | localStorage.setItem('angular2-todos', JSON.stringify(this.todos)); 80 | } 81 | 82 | _findByUid(uid) { 83 | return this.todos.find((todo) => todo.uid == uid); 84 | } 85 | 86 | _clearCache() { 87 | this.completedTodos = null; 88 | this.remainingTodos = null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/services/todo-store.service.spec.js: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { TodoStoreService } from './todo-store.service'; 4 | 5 | describe('TodoStoreService', function() { 6 | let subject; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | providers: [TodoStoreService] 11 | }); 12 | }); 13 | 14 | beforeEach(inject([TodoStoreService], (storage) => { 15 | subject = storage; 16 | })); 17 | 18 | it('should persist todos to localStorage', function() { 19 | spyOn(localStorage, 'setItem'); 20 | 21 | subject.persist(); 22 | 23 | expect(localStorage.setItem).toHaveBeenCalledWith('angular2-todos', '[]'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/style.js: -------------------------------------------------------------------------------- 1 | require('todomvc-common/base.css'); 2 | require('todomvc-app-css/index.css'); 3 | -------------------------------------------------------------------------------- /src/app/test.js: -------------------------------------------------------------------------------- 1 | import './polyfill'; 2 | 3 | import 'zone.js/dist/async-test'; 4 | import 'zone.js/dist/fake-async-test'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/proxy'; 7 | import 'zone.js/dist/jasmine-patch'; 8 | 9 | import { TestBed } from '@angular/core/testing'; 10 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | TestBed.initTestEnvironment( 13 | BrowserDynamicTestingModule, 14 | platformBrowserDynamicTesting() 15 | ); 16 | 17 | let testContext = require.context('./', true, /\.spec\.js/); 18 | testContext.keys().forEach(testContext); 19 | -------------------------------------------------------------------------------- /src/app/vendor.js: -------------------------------------------------------------------------------- 1 | import './polyfill'; 2 | import 'rxjs'; 3 | 4 | import 'uuid'; 5 | 6 | import '@angular/platform-browser'; 7 | import '@angular/platform-browser-dynamic'; 8 | import '@angular/forms'; 9 | import '@angular/http'; 10 | import '@angular/router'; 11 | import '@angular/core'; 12 | import '@angular/common'; 13 | import '@angular/compiler'; 14 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonicoder86/angular2-esnext-todomvc/2477ca256635b55cf6a4c28dd1348c25c861892d/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 2 TodoMVC 6 | 7 | 8 | 9 | 10 | 11 | 12 | Loading... 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /webpack.common.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let path = require('path'); 3 | 4 | module.exports = { 5 | context: path.join(process.cwd(), 'src'), 6 | 7 | resolve: { 8 | modules: [ 9 | 'node_modules', 10 | path.resolve(process.cwd(), 'src') 11 | ], 12 | extensions: ['.js', '.json'] 13 | }, 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.js$/, 19 | loader: 'babel-loader', 20 | exclude: /(node_modules)/, 21 | query: { 22 | presets: ['env', 'angular2'] 23 | } 24 | }, 25 | { 26 | test: /\.js$/, 27 | use: 'source-map-loader', 28 | exclude: [ 29 | path.join(process.cwd(), 'node_modules/rxjs'), 30 | path.join(process.cwd(), 'node_modules/@angular') 31 | ] 32 | }, 33 | { 34 | test: /\.html$/, 35 | use: 'html-loader?attrs=false&caseSensitive&removeAttributeQuotes=false' 36 | } 37 | ] 38 | }, 39 | 40 | stats: { 41 | errorDetails: true, 42 | colors: true, 43 | modules: true, 44 | reasons: true 45 | }, 46 | 47 | node: { 48 | global: true, 49 | crypto: 'empty', 50 | process: true, 51 | module: false, 52 | clearImmediate: false, 53 | setImmediate: false 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let commonConfig = require('./webpack.common.config'); 3 | let webpack = require('webpack'); 4 | let path = require('path'); 5 | let merge = require('webpack-merge'); 6 | let CopyWebpackPlugin = require('copy-webpack-plugin'); 7 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | 9 | let npmLifecycleEvent = process.env.npm_lifecycle_event || ''; 10 | let environment = process.env.NODE_ENV || ''; 11 | 12 | let contextReplacementPlugin = new webpack.ContextReplacementPlugin( 13 | // The (\\|\/) piece accounts for path separators in *nix and Windows 14 | /angular(\\|\/)core(\\|\/)@angular/, 15 | path.join(process.cwd(), 'src') 16 | ); 17 | let commonPlugins = [ 18 | new webpack.optimize.CommonsChunkPlugin({ name: ['main', 'vendor'], minChunks: Infinity }), 19 | new ExtractTextPlugin('style.bundle.css'), 20 | new webpack.ProgressPlugin(), 21 | contextReplacementPlugin 22 | ]; 23 | 24 | let cssLoader = { 25 | test: /\.css$/, 26 | loader: ExtractTextPlugin.extract({ 27 | fallback: 'style-loader', 28 | use: 'css-loader' 29 | }) 30 | }; 31 | 32 | let entry = { 33 | 'main': './app/main.js', 34 | 'vendor': './app/vendor.js', 35 | 'style': './app/style.js', 36 | }; 37 | 38 | let output = { 39 | path: path.resolve(process.cwd(), 'dist'), 40 | filename: '[name].bundle.js', 41 | sourceMapFilename: '[name].map', 42 | chunkFilename: '[id].chunk.js' 43 | }; 44 | 45 | if (npmLifecycleEvent == 'build' || environment == 'production') { 46 | module.exports = merge.smart(commonConfig, { 47 | entry: entry, 48 | output: output, 49 | 50 | plugins: commonPlugins.concat([ 51 | new webpack.LoaderOptionsPlugin({ 52 | debug: false, 53 | minimize: true 54 | }), 55 | new webpack.optimize.UglifyJsPlugin({ 56 | sourceMap: true, 57 | mangle: { screw_ie8 : true }, 58 | compress: { 59 | screw_ie8: true, 60 | warnings: false 61 | } 62 | }), 63 | new CopyWebpackPlugin([ 64 | { from: 'index.html' }, 65 | { from: 'favicon.ico' } 66 | ]) 67 | ]), 68 | 69 | module: { 70 | rules: [cssLoader] 71 | }, 72 | 73 | devtool: 'source-map' 74 | }); 75 | } else if (npmLifecycleEvent.indexOf('test') !== -1 || environment == 'test') { 76 | module.exports = merge.smart(commonConfig, { 77 | devtool: 'inline-source-map', 78 | 79 | plugins: [contextReplacementPlugin, new webpack.ProgressPlugin()] 80 | }); 81 | } else { 82 | module.exports = merge.smart(commonConfig, { 83 | entry: entry, 84 | output: output, 85 | 86 | plugins: commonPlugins, 87 | 88 | module: { 89 | rules: [cssLoader] 90 | }, 91 | 92 | devServer: { 93 | contentBase: './src', 94 | port: 3000, 95 | inline: true, 96 | historyApiFallback: true, 97 | watchOptions: { 98 | aggregateTimeout: 300, 99 | poll: 500 100 | }, 101 | stats: 'errors-only' 102 | }, 103 | 104 | devtool: 'cheap-module-source-map' 105 | }); 106 | } 107 | --------------------------------------------------------------------------------