Skip to content

Commit

Permalink
Remove Lodash (#1294)
Browse files Browse the repository at this point in the history
* chore: remove lodash from dnd-core

* chore: remove lodash from html5 backend

* chore: remove lodash from test backend

* chore: remove lodash from react-dnd

* docs: update FAQ info about using function components

* chore: rebuild yarn lock

* chore: upgrade tslint-react
  • Loading branch information
darthtrevino committed Mar 26, 2019
1 parent d2133ea commit 0a54e41
Show file tree
Hide file tree
Showing 28 changed files with 1,386 additions and 1,001 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -68,7 +68,7 @@
"ts-jest": "^24.0.0",
"tslint": "^5.13.1",
"tslint-config-prettier": "^1.18.0",
"tslint-react": "^3.6.0",
"tslint-react": "^4.0.0",
"typescript": "^3.3.3333"
},
"jest": {
Expand Down
1 change: 0 additions & 1 deletion packages/dnd-core/package.json
Expand Up @@ -20,7 +20,6 @@
"dependencies": {
"asap": "^2.0.6",
"invariant": "^2.2.4",
"lodash": "^4.17.11",
"redux": "^4.0.1"
},
"devDependencies": {
Expand Down
3 changes: 1 addition & 2 deletions packages/dnd-core/src/__tests__/DragDropManager.spec.ts
@@ -1,4 +1,3 @@
declare var require: any
declare var setImmediate: any

import createTestBackend, { TestBackend } from 'react-dnd-test-backend'
Expand All @@ -13,7 +12,7 @@ import {
} from './targets'
import DragDropManagerImpl from '../DragDropManagerImpl'
import { DragDropManager, HandlerRegistry } from '../interfaces'
const isString = require('lodash/isString')
import { isString } from '../utils/discount_lodash'

describe('DragDropManager', () => {
let manager: DragDropManager<any>
Expand Down
2 changes: 1 addition & 1 deletion packages/dnd-core/src/actions/dragDrop/beginDrag.ts
Expand Up @@ -10,8 +10,8 @@ import {
HandlerRegistry,
} from '../../interfaces'
import { setClientOffset } from './local/setClientOffset'
import { isObject } from '../../utils/discount_lodash'
const invariant = require('invariant')
const isObject = require('lodash/isObject')

import { BEGIN_DRAG, INIT_COORDS } from './types'

Expand Down
3 changes: 1 addition & 2 deletions packages/dnd-core/src/actions/dragDrop/drop.ts
Expand Up @@ -7,10 +7,9 @@ import {
Identifier,
} from '../../interfaces'
import { DROP } from './types'

import { isObject } from '../../utils/discount_lodash'
declare var require: any
const invariant = require('invariant')
const isObject = require('lodash/isObject')

export default function createDrop<Context>(manager: DragDropManager<Context>) {
return function drop(options = {}): void {
Expand Down
4 changes: 1 addition & 3 deletions packages/dnd-core/src/reducers/dirtyHandlerIds.ts
@@ -1,5 +1,3 @@
declare var require: any

import {
BEGIN_DRAG,
PUBLISH_DRAG_SOURCE,
Expand All @@ -16,7 +14,7 @@ import {
import { Action } from '../interfaces'
import { areArraysEqual } from '../utils/equality'
import { NONE, ALL } from '../utils/dirtiness'
const xor = require('lodash/xor')
import { xor } from '../utils/discount_lodash'

export type State = string[]

Expand Down
4 changes: 1 addition & 3 deletions packages/dnd-core/src/reducers/dragOperation.ts
@@ -1,5 +1,3 @@
declare var require: any

import {
BEGIN_DRAG,
PUBLISH_DRAG_SOURCE,
Expand All @@ -9,7 +7,7 @@ import {
} from '../actions/dragDrop'
import { REMOVE_TARGET } from '../actions/registry'
import { Identifier, Action } from '../interfaces'
const without = require('lodash/without')
import { without } from '../utils/discount_lodash'

export interface State {
itemType: Identifier | Identifier[] | null
Expand Down
6 changes: 2 additions & 4 deletions packages/dnd-core/src/reducers/index.ts
@@ -1,13 +1,11 @@
declare var require: any

import dragOffset, { State as DragOffsetState } from './dragOffset'
import dragOperation, { State as DragOperationState } from './dragOperation'
import refCount, { State as RefCountState } from './refCount'
import dirtyHandlerIds, {
State as DirtyHandlerIdsState,
} from './dirtyHandlerIds'
import stateId, { State as StateIdState } from './stateId'
const get = require('lodash/get')
import { get } from '../utils/discount_lodash'

export interface State {
dirtyHandlerIds: DirtyHandlerIdsState
Expand All @@ -23,7 +21,7 @@ export default function reduce(state: State = {} as State, action: any) {
type: action.type,
payload: {
...action.payload,
prevTargetIds: get(state, 'dragOperation.targetIds', []),
prevTargetIds: get<string[]>(state, 'dragOperation.targetIds', []),
},
}),
dragOffset: dragOffset(state.dragOffset, action),
Expand Down
13 changes: 13 additions & 0 deletions packages/dnd-core/src/utils/__tests__/utils.spec.ts
@@ -0,0 +1,13 @@
import { xor, intersection } from '../discount_lodash'

describe('the utilities module', () => {
it('can compute xor', () => {
const result = xor([1, 5, 7], [1, 3, 9, 7])
expect(result).toEqual([5, 3, 9])
})

it('can compute intersection', () => {
const result = intersection([1, 5, 7], [1, 3, 9, 7])
expect(result).toEqual([1, 7])
})
})
4 changes: 1 addition & 3 deletions packages/dnd-core/src/utils/dirtiness.ts
@@ -1,6 +1,4 @@
declare var require: any

const intersection = require('lodash/intersection')
import { intersection } from './discount_lodash'

export const NONE: string[] = []
export const ALL: string[] = []
Expand Down
64 changes: 64 additions & 0 deletions packages/dnd-core/src/utils/discount_lodash.ts
@@ -0,0 +1,64 @@
/**
* drop-in replacement for _.get
* @param obj
* @param path
* @param defaultValue
*/
export function get<T>(obj: any, path: string, defaultValue: T): T {
return path
.split('.')
.reduce((a, c) => (a && a[c] ? a[c] : defaultValue || null), obj) as T
}

/**
* drop-in replacement for _.without
*/
export function without<T>(items: T[], item: T) {
return items.filter(i => i !== item)
}

/**
* drop-in replacement for _.isString
* @param input
*/
export function isString(input: any) {
return typeof input === 'string'
}

/**
* drop-in replacement for _.isString
* @param input
*/
export function isObject(input: any) {
return typeof input === 'object'
}

/**
* repalcement for _.xor
* @param itemsA
* @param itemsB
*/
export function xor<T extends string | number>(itemsA: T[], itemsB: T[]): T[] {
const map = new Map<T, number>()
const insertItem = (item: T) =>
map.set(item, map.has(item) ? map.get(item)! + 1 : 1)
itemsA.forEach(insertItem)
itemsB.forEach(insertItem)

const result: T[] = []
map.forEach((count, key) => {
if (count === 1) {
result.push(key)
}
})
return result
}

/**
* replacement for _.intersection
* @param itemsA
* @param itemsB
*/
export function intersection<T>(itemsA: T[], itemsB: T[]) {
return itemsA.filter(t => itemsB.indexOf(t) > -1)
}
4 changes: 3 additions & 1 deletion packages/documentation/markdown/docs/00 Quick Start/FAQ.md
Expand Up @@ -43,7 +43,9 @@ Because [`DragSource`](/docs/api/drag-source) and [`DropTarget`](/docs/api/drop-

### Why is the `component` parameter always `null` in the `beginDrag`/`endDrag`/`drop`/`hover` methods?

When using [function components](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions), the `component` parameter will always be `null` in the `beginDrag`/`endDrag`/`drop`/`hover` methods. This is because it is not possible to attach a ref to a stateless function component as explained in [the React docs](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions).
When using [function components](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions), refs cannot normally be attached. However, if you use [React.forwardRef](https://reactjs.org/docs/forwarding-refs.html), then you can access the rendered component. If your component exposes an imperative API via the [useImperativeHandle hook](https://reactjs.org/docs/hooks-reference.html#useimperativehandle), then you can expose functionality that way.

If you use a tool like [babel-react-optimize](https://github.com/jamiebuilds/babel-react-optimize#transform-react-pure-class-to-function) preset or [babel-plugin-transform-react-pure-class-to-function](https://github.com/jamiebuilds/babel-react-optimize/tree/master/packages/babel-plugin-transform-react-pure-class-to-function), then be aware that your class definition may implicitly be transformed into a function component, which may result in a null argument.

```js
import { DragSource } from 'react-dnd'
Expand Down
3 changes: 1 addition & 2 deletions packages/react-dnd-html5-backend/package.json
Expand Up @@ -21,8 +21,7 @@
"start": "tsc -b tsconfig.esm.json -w --preserveWatchOutput"
},
"dependencies": {
"dnd-core": "^7.4.0",
"lodash": "^4.17.11"
"dnd-core": "^7.4.0"
},
"devDependencies": {
"@types/react": "^16.8.7",
Expand Down
4 changes: 1 addition & 3 deletions packages/react-dnd-html5-backend/src/BrowserDetector.ts
@@ -1,6 +1,4 @@
declare var require: any

const memoize = require('lodash/memoize')
import { memoize } from './utils/discount_lodash'

declare global {
// tslint:disable-next-line interface-name
Expand Down
10 changes: 3 additions & 7 deletions packages/react-dnd-html5-backend/src/EnterLeaveCounter.ts
@@ -1,16 +1,12 @@
declare var require: any

const union = require('lodash/union')
const without = require('lodash/without')

type NodePredicate = (node: any) => boolean;
import { union, without } from './utils/discount_lodash'
type NodePredicate = (node: any) => boolean

export default class EnterLeaveCounter {
private entered: any[] = []
private isNodeInDocument: NodePredicate

constructor(isNodeInDocument: NodePredicate) {
this.isNodeInDocument = isNodeInDocument;
this.isNodeInDocument = isNodeInDocument
}

public enter(enteringNode: any) {
Expand Down
13 changes: 6 additions & 7 deletions packages/react-dnd-html5-backend/src/HTML5Backend.ts
@@ -1,5 +1,3 @@
declare var require: any

import {
Backend,
DragDropManager,
Expand All @@ -22,7 +20,6 @@ import {
import * as NativeTypes from './NativeTypes'
import { HTML5BackendContext } from './interfaces'
import { NativeDragSource } from './NativeDragSources/NativeDragSource'
const defaults = require('lodash/defaults')

declare global {
// tslint:disable-next-line interface-name
Expand Down Expand Up @@ -216,9 +213,10 @@ export default class HTML5Backend implements Backend {
const sourceId = this.monitor.getSourceId() as string
const sourceNodeOptions = this.sourceNodeOptions.get(sourceId)

return defaults(sourceNodeOptions || {}, {
return {
dropEffect: this.altKeyPressed ? 'copy' : 'move',
})
...(sourceNodeOptions || {}),
}
}

private getCurrentDropEffect() {
Expand All @@ -234,11 +232,12 @@ export default class HTML5Backend implements Backend {
const sourceId = this.monitor.getSourceId() as string
const sourcePreviewNodeOptions = this.sourcePreviewNodeOptions.get(sourceId)

return defaults(sourcePreviewNodeOptions || {}, {
return {
anchorX: 0.5,
anchorY: 0.5,
captureDraggingState: false,
})
...(sourcePreviewNodeOptions || {}),
}
}

private getSourceClientOffset = (sourceId: string) => {
Expand Down
@@ -0,0 +1,23 @@
import { memoize, union } from '../discount_lodash'

describe('lodash replacement', () => {
it('can memoize', () => {
let count = 0
const fn = memoize(() => {
if (count > 0) {
throw new Error('too many invocations to memoized function')
}
count += 1
return 100 + 30
})

expect(fn()).toEqual(130)
expect(fn()).toEqual(130)
expect(fn()).toEqual(130)
})

it('can compute union', () => {
const result = union([1, 2, 1, 3, 1, 5], [7, 8, 7, 9])
expect(result).toEqual([1, 2, 3, 5, 7, 8, 9])
})
})
28 changes: 28 additions & 0 deletions packages/react-dnd-html5-backend/src/utils/discount_lodash.ts
@@ -0,0 +1,28 @@
export function memoize<T>(fn: () => T): () => T {
let result: T | null = null
const memoized = () => {
if (result == null) {
result = fn()
}
return result
}
return memoized
}

/**
* drop-in replacement for _.without
*/
export function without<T>(items: T[], item: T) {
return items.filter(i => i !== item)
}

export function union<T extends string | number>(itemsA: T[], itemsB: T[]) {
const set = new Set<T>()
const insertItem = (item: T) => set.add(item)
itemsA.forEach(insertItem)
itemsB.forEach(insertItem)

const result: T[] = []
set.forEach(key => result.push(key))
return result
}
3 changes: 1 addition & 2 deletions packages/react-dnd-test-backend/package.json
Expand Up @@ -18,8 +18,7 @@
"prepublish": "npm run test"
},
"dependencies": {
"dnd-core": "^7.4.0",
"lodash": "^4.17.11"
"dnd-core": "^7.4.0"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
Expand Down
7 changes: 4 additions & 3 deletions packages/react-dnd-test-backend/src/TestBackend.ts
@@ -1,5 +1,3 @@
declare var require: any

import {
DragDropManager,
DragDropActions,
Expand All @@ -8,7 +6,10 @@ import {
HoverOptions,
Identifier,
} from 'dnd-core'
const noop = require('lodash/noop')

function noop() {
// noop
}

export interface TestBackend {
didCallSetup: boolean
Expand Down
1 change: 0 additions & 1 deletion packages/react-dnd/package.json
Expand Up @@ -24,7 +24,6 @@
"dnd-core": "^7.4.0",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.1.0",
"lodash": "^4.17.11",
"shallowequal": "^1.1.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-dnd/src/DragSource.ts
Expand Up @@ -10,8 +10,8 @@ import DragSourceMonitorImpl from './DragSourceMonitorImpl'
import SourceConnector from './SourceConnector'
import isValidType from './utils/isValidType'
import { DndComponentEnhancer } from './interfaces'
import { isPlainObject } from './utils/discount_lodash'
const invariant = require('invariant')
const isPlainObject = require('lodash/isPlainObject')

/**
* Decorates a component as a dragsource
Expand Down
2 changes: 1 addition & 1 deletion packages/react-dnd/src/DropTarget.ts
Expand Up @@ -15,8 +15,8 @@ import createTargetFactory from './createTargetFactory'
import isValidType from './utils/isValidType'
import DropTargetMonitorImpl from './DropTargetMonitorImpl'
import TargetConnector from './TargetConnector'
import { isPlainObject } from './utils/discount_lodash'
const invariant = require('invariant')
const isPlainObject = require('lodash/isPlainObject')

export default function DropTarget<RequiredProps, CollectedProps = {}>(
type: TargetType | ((props: RequiredProps) => TargetType),
Expand Down

0 comments on commit 0a54e41

Please sign in to comment.