react-magnetic-di
Advanced tools
Comparing version 1.0.0 to 2.0.0
@@ -6,25 +6,31 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "provideDependencies", { | ||
Object.defineProperty(exports, "di", { | ||
enumerable: true, | ||
get: function get() { | ||
return _provideDeps.provideDependencies; | ||
return _consumer.di; | ||
} | ||
}); | ||
Object.defineProperty(exports, "DependencyProvider", { | ||
Object.defineProperty(exports, "DiProvider", { | ||
enumerable: true, | ||
get: function get() { | ||
return _provider.DependencyProvider; | ||
return _provider.DiProvider; | ||
} | ||
}); | ||
Object.defineProperty(exports, "settings", { | ||
Object.defineProperty(exports, "withDi", { | ||
enumerable: true, | ||
get: function get() { | ||
return _settings.settings; | ||
return _provider.withDi; | ||
} | ||
}); | ||
Object.defineProperty(exports, "mock", { | ||
enumerable: true, | ||
get: function get() { | ||
return _utils.mock; | ||
} | ||
}); | ||
var _provideDeps = require("./provide-deps"); | ||
var _consumer = require("./react/consumer"); | ||
var _provider = require("./provider"); | ||
var _provider = require("./react/provider"); | ||
var _settings = require("./settings"); | ||
var _utils = require("./react/utils"); |
@@ -1,3 +0,3 @@ | ||
export { provideDependencies } from './provide-deps'; | ||
export { DependencyProvider } from './provider'; | ||
export { settings } from './settings'; | ||
export { di } from './react/consumer'; | ||
export { DiProvider, withDi } from './react/provider'; | ||
export { mock } from './react/utils'; |
{ | ||
"name": "react-magnetic-di", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Context driven dependency injection", | ||
@@ -11,2 +11,3 @@ "keywords": [ | ||
"module": "lib/esm/index.js", | ||
"types": "./types/index.d.ts", | ||
"repository": { | ||
@@ -23,6 +24,7 @@ "type": "git", | ||
"scripts": { | ||
"clean:build": "rm -rf ./lib", | ||
"clean:build": "rm -rf ./lib && rm -rf ./babel", | ||
"build:cjs": "babel src/ -d lib/cjs --ignore **/__tests__ --presets @babel/env", | ||
"build:esm": "babel src/ -d lib/esm --ignore **/__tests__", | ||
"build:flow": "echo lib/cjs lib/esm | xargs -n 1 cp src/index.js.flow", | ||
"build:babel": "mkdir babel && cp src/babel/index.js babel", | ||
"build": "npm run clean:build -s && npm run build:cjs -s && npm run build:esm -s && npm run build:flow -s", | ||
@@ -39,37 +41,34 @@ "test": "jest", | ||
"prop-types": "^15.0.0", | ||
"react": "^16.4.0" | ||
"react": "^16.9.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.2.3", | ||
"@babel/core": "^7.4.0", | ||
"@babel/plugin-proposal-class-properties": "^7.4.0", | ||
"@babel/plugin-proposal-export-namespace-from": "^7.2.0", | ||
"@babel/plugin-syntax-dynamic-import": "^7.2.0", | ||
"@babel/plugin-syntax-import-meta": "^7.2.0", | ||
"@babel/plugin-transform-runtime": "^7.4.0", | ||
"@babel/preset-env": "^7.4.1", | ||
"@babel/preset-flow": "^7.0.0", | ||
"@babel/preset-react": "^7.0.0", | ||
"@babel/runtime": "~7.4.0", | ||
"babel-eslint": "^10.0.1", | ||
"babel-jest": "^24.1.0", | ||
"babel-loader": "^8.0.5", | ||
"enzyme": "~3.9.0", | ||
"enzyme-adapter-react-16": "^1.11.2", | ||
"eslint": "^5.15.2", | ||
"eslint-plugin-flowtype": "^3.4.2", | ||
"eslint-plugin-import": "^2.16.0", | ||
"eslint-plugin-react": "^7.12.4", | ||
"eslint-plugin-react-hooks": "^1.5.1", | ||
"flow-bin": "^0.97.0", | ||
"flow-copy-source": "^2.0.3", | ||
"jest": "^24.5.0", | ||
"prettier": "^1.16.4", | ||
"prop-types": "~15.7.2", | ||
"react": "^16.8.6", | ||
"react-dom": "^16.8.6", | ||
"webpack": "^4.29.6", | ||
"webpack-cli": "^3.2.3", | ||
"webpack-dev-server": "^3.1.14" | ||
"@babel/cli": "^7.8.4", | ||
"@babel/core": "^7.9.6", | ||
"@babel/plugin-proposal-class-properties": "^7.8.3", | ||
"@babel/plugin-transform-runtime": "^7.9.6", | ||
"@babel/preset-env": "^7.9.6", | ||
"@babel/preset-flow": "^7.9.0", | ||
"@babel/preset-react": "^7.9.4", | ||
"@babel/runtime": "^7.9.6", | ||
"babel-eslint": "^10.1.0", | ||
"babel-jest": "^26.0.1", | ||
"babel-loader": "^8.1.0", | ||
"enzyme": "~3.11.0", | ||
"enzyme-adapter-react-16": "^1.15.0", | ||
"eslint": "^7.0.0", | ||
"eslint-plugin-flowtype": "^5.1.0", | ||
"eslint-plugin-import": "^2.20.2", | ||
"eslint-plugin-react": "^7.20.0", | ||
"eslint-plugin-react-hooks": "^4.0.2", | ||
"flow-bin": "^0.125.1", | ||
"flow-copy-source": "^2.0.9", | ||
"jest": "^26.0.1", | ||
"prettier": "^2.0.5", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.13.1", | ||
"react-dom": "^16.13.1", | ||
"webpack": "^4.43.0", | ||
"webpack-cli": "^3.3.11", | ||
"webpack-dev-server": "^3.11.0" | ||
} | ||
} |
140
README.md
@@ -11,9 +11,8 @@ # react-magnetic-di | ||
- Nearly-zero performance overhead | ||
- Works with any kind of dependency (not only components) | ||
- Targets dependencies at any level of the tree | ||
- Close-to-zero performance overhead | ||
- Works with any kind of functions/classes (not only components) and in both class and functional components | ||
- Replaces dependencies across the entire tree | ||
- Allows selective injection | ||
- Enforces separation of concerns | ||
- Does not mess up with React internals (just uses Context) | ||
- Can also be enabled in prod, at small cost (off by default) | ||
- Uses Context in light way (not messing up with React internals) | ||
@@ -24,14 +23,8 @@ ## Philosophy | ||
A common pattern to solve this problem is injecting those "dependencies" in the component via props. However, that approach has a some of downsides: | ||
A common pattern to solve this problem is injecting those "dependencies" in the component via props. However, that approach has a some of downsides, like leaking internal implementation details, mixing together injected dependencies with other props and introducing additional complexity when typing props. | ||
1. We are leaking internal implementation details. Props are the public API of a component and we are polluting them with keys that are not relevant for actual consumers, and we are doing that just for our testing needs | ||
`react-magnetic-di` takes inspiration from decorators, and with a light touch of Babel magic and React Context allows you to optionally override such dependencies, with nearly-zero performane overhead (when not using it). | ||
2. Our dependencies are mixed together with other props, which makes them hard to recognise, analyse and might introduce naming conflicts | ||
## Usage | ||
3. It introduces additional complexity, for instance when we have spread props down, as we probably don't want pass the dependencies down too | ||
`react-magnetic-di` takes inspiration from React PropTypes, forcing you to declare the dependencies statically and then using React Context to optionally override those dependencies, with nearly-zero performane overhead when the injection system is off. | ||
## Basic usage | ||
```sh | ||
@@ -43,19 +36,30 @@ npm i react-magnetic-di | ||
### Adding babel plugin | ||
Edit your Babel config file (`.babel.rc` / `babel.config.js` / ...) and add: | ||
```js | ||
// ... other stuff like presets | ||
plugins: [ | ||
// ... other plugins | ||
'react-magnetic-di/babel', | ||
], | ||
``` | ||
### Using dependency injection in your components | ||
Given a component with complex UI interaction or data dependencies, like a Modal or an Apollo Query, we want to be able integration test it without necessarily test those other dependencies. | ||
To achieve that, we define the dependencies on the class component: | ||
To achieve that, we mark such dependencies in the `render` function of the class component: | ||
```js | ||
```jsx | ||
import React, { Component } from 'react'; | ||
import { provideDependencies } from 'react-magnetic-di'; | ||
import { Modal as ModalDI } from 'material-ui'; | ||
import { Query as QueryDI } from 'react-apollo'; | ||
import { di } from 'react-magnetic-di'; | ||
import { Modal } from 'material-ui'; | ||
import { Query } from 'react-apollo'; | ||
class MyComponent extends Component { | ||
static dependencies = provideDependencies({ | ||
Modal: ModalDI, | ||
Query: QueryDI, | ||
}); | ||
render() { | ||
// that's all is needed to "mark" these variables as injectable | ||
di(Modal, Query); | ||
render() { | ||
const { Modal, Query } = MyComponent.dependencies(); | ||
return ( | ||
@@ -72,67 +76,63 @@ <Modal> | ||
```js | ||
```jsx | ||
import React, { Component } from 'react'; | ||
import { provideDependencies } from 'react-magnetic-di'; | ||
import { Modal as ModalDI } from 'material-ui'; | ||
import { useQuery as useQueryDI } from 'react-apollo-hooks'; | ||
import { di } from 'react-magnetic-di'; | ||
import { Modal } from 'material-ui'; | ||
import { useQuery } from 'react-apollo-hooks'; | ||
function MyComponent() { | ||
const { Modal, useQuery } = MyComponent.dependencies(); | ||
// "mark" any type of function/class as injectable | ||
di(Modal, useQuery); | ||
const { data } = useQuery(); | ||
return <Modal>{data && 'Done!'}</Modal>; | ||
} | ||
MyComponent.dependencies = provideDependencies({ | ||
Modal: ModalDI, | ||
useQuery: useQueryDI, | ||
}); | ||
``` | ||
Finally, in the integration tests and storybooks we wrap the component with `DependencyProvider` to override any dependency: | ||
### Leveraging dependency injection in tests and storybooks | ||
```js | ||
In the unit/integration tests or storybooks we can create a mock implementation and wrap the component with `DiProvider` to override any dependency: | ||
```jsx | ||
import React from 'react'; | ||
import { DependencyProvider } from 'react-magnetic-di'; | ||
import { DiProvider, di } from 'react-magnetic-di'; | ||
import { Modal } from 'material-ui'; | ||
import { useQuery } from 'react-apollo-hooks'; | ||
import { ModalOpen, useQueryMock } from './examples'; | ||
// mock() accepts the original implementation as first argument | ||
// and the replacement implementation as second | ||
const ModalOpen = di.mock(Modal, () => <div />); | ||
const useQueryMock = di.mock(useQuery, () => ({ data: null })); | ||
// test-enzyme.js | ||
it('should render with enzyme', () => { | ||
const container = mount(<MyComponent />, { | ||
wrappingComponent: DiProvider, | ||
wrappingComponentProps: { use: [ModalOpen, useQuery] }, | ||
}); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
// test-testing-library.js | ||
it('should render with react-testing-library', () => { | ||
const { container } = render(<MyComponent />, { | ||
wrapper: (p) => <DiProvider use={[ModalOpen, useQuery]} {...p} />, | ||
}); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
// story.js | ||
storiesOf('Modal content', module).add('with text', () => ( | ||
<DependencyProvider use={{ Modal: ModalOpen, useQuery: useQueryMock }}> | ||
<DiProvider use={[ModalOpen, useQuery]}> | ||
<MyComponent /> | ||
</DependencyProvider> | ||
</DiProvider> | ||
)); | ||
``` | ||
In the example above we replace all `Modal` dependencies across all components in the tree with the open version, but that might be wrong for `useQuery`, as we might want to provide different data to different components. `DependencyProvider` enables targeted dependency injection via `target` prop. Together with providers composition it allows multiple, explicit dependency injections: | ||
In the example above we replace all `Modal` and `useQuery` dependencies across all components in the tree with the custom versions. | ||
```js | ||
storiesOf('App', module).add('with text', () => ( | ||
/* replace Modal on all components */ | ||
<DependencyProvider use={{ Modal: ModalOpen }}> | ||
{/* in MyComponent use one set of data */} | ||
<DependencyProvider target={MyComponent} use={{ useQuery: useQueryMock }}> | ||
{/* in MyOtherComponent use a different set of data */} | ||
<DependencyProvider | ||
target={MyOtherComponent} | ||
use={{ useQuery: useQueryOtherMock }} | ||
> | ||
<MyApp /> | ||
</DependencyProvider> | ||
</DependencyProvider> | ||
</DependencyProvider> | ||
)); | ||
``` | ||
## FAQ | ||
## Settings | ||
- Can I replace only one instance for one component? Currently no, but it could be possible. | ||
#### enabled | ||
Defines whenever context replacement is allowed or not. By default is `NODE_ENV !== 'production'`. It can be enabled also in prod, but it is not recommended. | ||
```js | ||
import { settings } from 'react-magnetic-di'; | ||
// only enable during testing | ||
setting.enabled = process.env.NODE_ENV === 'test'; | ||
``` | ||
## Contributing | ||
@@ -139,0 +139,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32451
28
20
428
1