Add todoapp benchmark

This commit is contained in:
Roman Vishnevskii 2017-04-24 16:00:32 +07:00
commit 02e7b40c07
218 changed files with 63168 additions and 0 deletions

View file

@ -0,0 +1 @@
(function(){"use strict";angular.module("todoCtrl",[]).controller("TodoCtrl",function TodoCtrl($scope,$location){var TC=this;var todos=TC.todos=[];TC.ESCAPE_KEY=27;TC.editedTodo={};TC.updateRemainingCount=function(){TC.remainingCount=todos.filter(function(todo){return!todo.completed}).length;TC.allChecked=TC.remainingCount===0};function resetTodo(){TC.newTodo={title:"",completed:false}}resetTodo();if($location.path()===""){$location.path("/")}TC.location=$location;$scope.$watch("TC.location.path()",function(path){TC.statusFilter={"/active":{completed:false},"/completed":{completed:true}}[path]});TC.addTodo=function(){var newTitle=TC.newTodo.title=TC.newTodo.title.trim();if(newTitle.length===0){return}todos.push(TC.newTodo);resetTodo();TC.updateRemainingCount()};TC.editTodo=function(todo){TC.editedTodo=todo;TC.originalTodo=angular.copy(todo)};TC.doneEditing=function(todo,index){TC.editedTodo={};todo.title=todo.title.trim();if(!todo.title){TC.removeTodo(index)}TC.updateRemainingCount()};TC.revertEditing=function(index){TC.editedTodo={};todos[index]=TC.originalTodo;TC.updateRemainingCount()};TC.removeTodo=function(index){todos.splice(index,1);TC.updateRemainingCount()};TC.clearCompletedTodos=function(){TC.todos=todos=todos.filter(function(val){return!val.completed});TC.updateRemainingCount()};TC.markAll=function(completed){todos.forEach(function(todo){todo.completed=completed});TC.updateRemainingCount()}});angular.module("todoFocus",[]).directive("todoFocus",function($timeout){return function(scope,elem,attrs){scope.$watch(attrs.todoFocus,function(newVal){if(newVal){$timeout(function(){elem[0].focus()},0,false)}})}});angular.module("todomvc",["todoCtrl","todoFocus"])})();

View file

@ -0,0 +1,77 @@
<!doctype html>
<html lang="en" ng-app="todomvc" data-framework="angularjs">
<head>
<meta charset="utf-8">
<title>AngularJS • TodoMVC</title>
<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
<style>
[ng-cloak] {
display: none;
}
</style>
</head>
<body>
<section class="todoapp" ng-controller="TodoCtrl as TC">
<header class="header">
<h1>todos</h1>
<form id="todo-form">
<input class="new-todo" placeholder="What needs to be done?" ng-model="TC.newTodo.title" autofocus ng-keydown="($event.keyCode === 13) && TC.addTodo()">
</form>
</header>
<section class="main" ng-show="TC.todos.length" ng-cloak>
<input class="toggle-all" type="checkbox" ng-model="TC.allChecked" ng-click="TC.markAll(TC.allChecked)">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li ng-repeat="todo in TC.todos | filter:TC.statusFilter" ng-class="{completed: todo.completed, editing: todo === TC.editedTodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed" ng-change="TC.updateAndStore()">
<label ng-dblclick="TC.editTodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="TC.removeTodo($index)"></button>
</div>
<form ng-submit="TC.doneEditing(todo, $index)">
<input class="edit" ng-trim="false" ng-model="todo.title" ng-blur="TC.doneEditing(todo, $index)" ng-keydown="($event.keyCode === TC.ESCAPE_KEY) && TC.revertEditing($index)" todo-focus="todo === TC.editedTodo">
</form>
</li>
</ul>
</section>
<footer class="footer" ng-show="TC.todos.length" ng-cloak>
<span class="todo-count"><strong>{{TC.remainingCount}}</strong>
<ng-pluralize count="TC.remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul class="filters">
<li>
<a ng-class="{selected: TC.location.path() == '/'} " href="#/">All</a>
</li>
<li>
<a ng-class="{selected: TC.location.path() == '/active'}" href="#/active">Active</a>
</li>
<li>
<a ng-class="{selected: TC.location.path() == '/completed'}" href="#/completed">Completed</a>
</li>
</ul>
<button class="clear-completed" ng-click="TC.clearCompletedTodos()" ng-show="TC.remainingCount < TC.todos.length">Clear completed</button>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Credits:
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>,
<a href="http://ericbidelman.com">Eric Bidelman</a>,
<a href="http://jacobmumm.com">Jacob Mumm</a> and
<a href="http://blog.igorminar.com">Igor Minar</a>
</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script>
window.requestAnimationFrame = window.webkitRequestAnimationFrame = function(callback) {
callback();
};
</script>
<script src="node_modules/angular/angular.js"></script>
<script src="build.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,118 @@
/* jshint undef: true, unused: true */
/*global angular */
(function () {
'use strict';
angular.module('todoCtrl', [])
/**
* The main controller for the app. The controller:
* - retrieves and persists the model via the todoStorage service
* - exposes the model to the template and provides event handlers
*/
.controller('TodoCtrl', function TodoCtrl($scope, $location) {
var TC = this;
var todos = TC.todos = [];
TC.ESCAPE_KEY = 27;
TC.editedTodo = {};
TC.updateRemainingCount = function () {
TC.remainingCount = todos.filter(function (todo) { return !todo.completed; }).length;
TC.allChecked = (TC.remainingCount === 0);
}
function resetTodo() {
TC.newTodo = {title: '', completed: false};
}
resetTodo();
if ($location.path() === '') {
$location.path('/');
}
TC.location = $location;
$scope.$watch('TC.location.path()', function (path) {
TC.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path];
});
TC.addTodo = function () {
var newTitle = TC.newTodo.title = TC.newTodo.title.trim();
if (newTitle.length === 0) {
return;
}
todos.push(TC.newTodo);
resetTodo();
TC.updateRemainingCount();
};
TC.editTodo = function (todo) {
TC.editedTodo = todo;
// Clone the original todo to restore it on demand.
TC.originalTodo = angular.copy(todo);
};
TC.doneEditing = function (todo, index) {
TC.editedTodo = {};
todo.title = todo.title.trim();
if (!todo.title) {
TC.removeTodo(index);
}
TC.updateRemainingCount();
};
TC.revertEditing = function (index) {
TC.editedTodo = {};
todos[index] = TC.originalTodo;
TC.updateRemainingCount();
};
TC.removeTodo = function (index) {
todos.splice(index, 1);
TC.updateRemainingCount();
};
TC.clearCompletedTodos = function () {
TC.todos = todos = todos.filter(function (val) {
return !val.completed;
});
TC.updateRemainingCount();
};
TC.markAll = function (completed) {
todos.forEach(function (todo) {
todo.completed = completed;
});
TC.updateRemainingCount();
};
});
angular.module('todoFocus', [])
/**
* Directive that places focus on the element it is applied to when the expression it binds to evaluates to true
*/
.directive('todoFocus', function ($timeout) {
return function (scope, elem, attrs) {
scope.$watch(attrs.todoFocus, function (newVal) {
if (newVal) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
});
/**
* The main TodoMVC app module that pulls all dependency modules declared in same named files
*
* @type {angular.Module}
*/
angular.module('todomvc', ['todoCtrl', 'todoFocus']);
})();

View file

@ -0,0 +1,14 @@
{
"private": true,
"scripts": {
"make": "uglifyjs js/app.js -o build.min.js"
},
"dependencies": {
"angular": "^1.5.8",
"todomvc-app-css": "^2.0.6",
"todomvc-common": "^1.0.2"
},
"devDependencies": {
"uglify-js": "^2.6.4"
}
}

View file

@ -0,0 +1,25 @@
# Angular TodoMVC for Benchmarking
This is an update of the
_[Angular TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/angularjs-perf)_
with the following changes:
* Upgrade to Angular 1.5.7
* Update todomvc-app-css to 2.0.6 to match other examples in this benchmark
* Remove localStorage functionality - not relevant to measuring render performance
* Concat JS into one file
* Minify with uglify
and additional performance optimizations:
#### Removed the deep $watch in favor of direct function calls
The original example uses $scope.$watch with object equality enabled to track changes to the array of todos and perform actions when changes are detected. This is not a good practice as it causes a deep array comparison to happen every digest cycle, which can significantly degrade performance when working with large collections. Instead we can perform the required actions directly whenever todos are modified, since we control all points of possible modification.
#### Removed "track by $index" from the ng-repeat directive
The original example uses the ng-repeat directive with a "track by $index" clause to display the list of todos. _[Refer to section "Tracking and Duplicates" here for information on using "track by".](https://code.angularjs.org/1.5.7/docs/api/ng/directive/ngRepeat)_ In this particular case it is not necessary to use it, as long as care is taken not to persist the "$$hashKey" property added by Angular to local storage, which would cause duplicates to appear when the collection is loaded and new items are added, triggering an exception. The version without the "track by" clause could reasonably be written by someone implementing this from scratch and demonstrates better performance.
# Building
1. `npm install`
2. `npm run make`
3. Open a local server (e.g. with `npm install -g http-server`) and open index.html