diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce96fd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Created by .ignore support plugin (hsz.mobi) + +.idea diff --git a/400.png b/400.png new file mode 100644 index 0000000..274eaed Binary files /dev/null and b/400.png differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..618504c --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Cumulocity lodash 4.x Migration (and Style) Guide + +## Preferable Functional Methods +Conventionally speaking, we should favor lodash methods over their native and/or Angular counterparts. + +> Why? +- [lodash blows away the performance of native functional methods.](http://benmccormick.org/2014/11/12/underscore-vs-lodash/) +- lodash has more features for those functional methods such as _chaining_ and _iteratee shorthand_. +- lodash provides compatibility consistency for browser which hasn't really natively supported these functions yet (read: IE). +- lodash enables us to produce code in better clarity (and of course – improve style consistency :wink:), e.g. compare: `Object.hasOwnProperty` vs `_.has`, `angular.forEach` vs `_.forEach`. + +![http://cloud.highcharts.com/images/utusen/0/400.pdf](400.png) + + +Here are the two equivalence tables (note that the lists maybe incomplete, I only extract the common ones we use in our code): + +### Native +| Native method | lodash method | +| --- | --- | +| `Function.prototype.bind` | `_.bind` | +| `Array.prototype.forEach` | `_.forEach` | +| `Array.prototype.map` | `_.map` | +| `Array.prototype.filter` | `_.filter` | +| `Array.prototype.reduce` | `_.reduce` | +| `Object.hasOwnProperty` | `_.has` | +| `Object.keys` | `_.keys` | +| `Object.values` | `_.values` | + +### Angular +| Angular method | lodash method | +| --- | --- | +| `angular.bind` (beware that Angular [`.bind`](https://docs.angularjs.org/api/ng/function/angular.bind) function signature expects different arguments order) | `_.bind` | +| `angular.copy` | `_.cloneDeep` | +| `angular.equals` | `_.isEqual` | +| `angular.extend` | `_.assign` | +| `angular.forEach` | `_.forEach` | +| `angular.identity` | `_.identity` | +| `angular.isArray` | `_.isArray` | +| `angular.isDate` | `_.isDate` | +| `angular.isDefined` | `!_.isUndefined` | +| `angular.isElement` | `_.isElement` | +| `angular.isFunction` | `_.isFunction` | +| `angular.isNumber` | `_.isNumber` | +| `angular.isObject` | `_.isObject` | +| `angular.isString` | `_.isString` | +| `angular.isUndefined` | `_.isUndefined` | +| `angular.merge` | `_.merge` | +| `angular.noop` | `_.noop` | + +## Preferable lodash Functional Method Aliases +In our code, lodash functional method aliases have been used inconsistently. :disappointed: So, please help yourself to make it more consistent! + +| Alias | Favorable alias | +| --- | --- | +| `_.all` | `_.every` | +| `_.any` | `_.some` | +| `_.each` | `_.forEach` | +| `_.extend` (beware that lodash 4.x [`.extend`](https://lodash.com/docs/4.16.2#assignIn) behaves differently and **not** an alias for `.assign`) | `_.assign` | +| `_.unique` | `_.uniq` | +| `_.chain` | `_()` (yup, prefer implicit chaining. Also, beware that in implicit chaining, the wrapper [methods that are not chainable](https://lodash.com/docs/4.16.2#lodash) will end the chaining without the need of calling `.value()`, e.g. `.forEach`, `.reduce`) | + +## Breaking Changes in 4.x (Method Removals and Renames) +[R.T.F. Changelog](https://github.com/lodash/lodash/wiki/Changelog#v400). :shipit: + +Known to be used in our project are: +- `_.include` -> `_.includes` +- `_.contains` -> `_.includes` +- `_.pluck` -> `_.map` with iteratee shorthand +- `_.where` -> `_.filter` with iteratee shorthand +- `_.first` -> `_.head` +- `_.invoke` -> `_.invokeMap` +- `_.rest` -> `_.tail` diff --git a/migrate-lodash.js b/migrate-lodash.js new file mode 100644 index 0000000..c8aa26b --- /dev/null +++ b/migrate-lodash.js @@ -0,0 +1,140 @@ +const _ = require('lodash'); +const replace = require('replace'); + +const replacementSpecs = [ + { + regex: 'Object[.]hasOwnProperty', + replacement: '_.has', + }, + { + regex: 'Object[.]keys', + replacement: '_.keys', + }, + { + regex: 'Object[.]values', + replacement: '_.values', + }, + { + regex: 'angular[.]copy', + replacement: '_.cloneDeep', + }, + { + regex: 'angular[.]equals', + replacement: '_.isEqual', + }, + { + regex: 'angular[.]extend', + replacement: '_.assign', + }, + { + regex: 'angular[.]forEach', + replacement: '_.forEach', + }, + { + regex: 'angular[.]identity', + replacement: '_.identity', + }, + { + regex: 'angular[.]isArray', + replacement: '_.isArray', + }, + { + regex: 'angular[.]isDate', + replacement: '_.isDate', + }, + { + regex: 'angular[.]isDefined', + replacement: '!_.isUndefined', + }, + { + regex: 'angular[.]isElement', + replacement: '_.isElement', + }, + { + regex: 'angular[.]isFunction', + replacement: '_.isFunction', + }, + { + regex: 'angular[.]isNumber', + replacement: '_.isNumber', + }, + { + regex: 'angular[.]isObject', + replacement: '_.isObject', + }, + { + regex: 'angular[.]isString', + replacement: '_.isString', + }, + { + regex: 'angular[.]isUndefined', + replacement: '_.isUndefined', + }, + { + regex: 'angular[.]merge', + replacement: '_.merge', + }, + { + regex: 'angular[.]noop', + replacement: '_.noop', + }, + { + regex: '_[.]all', + replacement: '_.every', + }, + { + regex: '_[.]any', + replacement: '_.some', + }, + { + regex: '_[.]each', + replacement: '_.forEach', + }, + { + regex: '_[.]extend', + replacement: '_.assign', + }, + { + regex: '_[.]unique', + replacement: '_.uniq', + }, + { + regex: '_[.]chain', + replacement: '_', + }, + { + regex: '_[.]include', + replacement: '_.includes', + }, + { + regex: '_[.]contains', + replacement: '_.includes', + }, + { + regex: '_[.]pluck', + replacement: '_.map', + }, + { + regex: '_[.]where', + replacement: '_.filter', + }, + { + regex: '_[.]first', + replacement: '_.head', + }, + { + regex: '_[.]invoke', + replacement: '_.invokeMap', + }, + { + regex: '_[.]rest', + replacement: '_.tail', + }, +]; + +_.forEach(replacementSpecs, replacementSpec => replace({ + regex: replacementSpec.regex, + replacement: replacementSpec.replacement, + paths: ['/Users/glenn/cumulocity/cumulocity-ui/app/scripts'], + recursive: true, +}));