react-magnetic-di
Advanced tools
Comparing version 2.2.13 to 2.3.0
@@ -6,4 +6,4 @@ "use strict"; | ||
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } | ||
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } | ||
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } | ||
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } | ||
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i["return"] && (_r = _i["return"](), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } } | ||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } | ||
@@ -10,0 +10,0 @@ var _require = require('../utils'), |
@@ -30,2 +30,14 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "runWithDi", { | ||
enumerable: true, | ||
get: function get() { | ||
return _global.runWithDi; | ||
} | ||
}); | ||
Object.defineProperty(exports, "stats", { | ||
enumerable: true, | ||
get: function get() { | ||
return _stats.stats; | ||
} | ||
}); | ||
Object.defineProperty(exports, "withDi", { | ||
@@ -38,3 +50,5 @@ enumerable: true, | ||
var _consumer = require("./react/consumer"); | ||
var _global = require("./react/global"); | ||
var _provider = require("./react/provider"); | ||
var _utils = require("./react/utils"); | ||
var _utils = require("./react/utils"); | ||
var _stats = require("./react/stats"); |
@@ -9,2 +9,3 @@ "use strict"; | ||
var _context = require("./context"); | ||
var _global = require("./global"); | ||
var _utils = require("./utils"); | ||
@@ -19,7 +20,4 @@ function di(deps, target) { | ||
// grab value from default renderer | ||
_context.Context._currentValue || {}, | ||
_ref$getDependencies = _ref.getDependencies, | ||
getDependencies = _ref$getDependencies === void 0 ? function (v) { | ||
return v; | ||
} : _ref$getDependencies; | ||
_context.Context._currentValue || _global.globalDi, | ||
getDependencies = _ref.getDependencies; | ||
return getDependencies(deps, target); | ||
@@ -26,0 +24,0 @@ } else { |
@@ -8,8 +8,7 @@ "use strict"; | ||
var _react = _interopRequireDefault(require("react")); | ||
var _global = require("./global"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
var Context = /*#__PURE__*/_react["default"].createContext({ | ||
getDependencies: function getDependencies(deps) { | ||
return deps; | ||
} | ||
getDependencies: _global.globalDi.getDependencies | ||
}); | ||
exports.Context = Context; |
@@ -13,2 +13,3 @@ "use strict"; | ||
var _context = require("./context"); | ||
var _stats = require("./stats"); | ||
var _utils = require("./utils"); | ||
@@ -30,2 +31,3 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
var replacementMap = use.reduce(function (m, d) { | ||
_stats.stats.set(d); | ||
return m.set(d[_constants.KEY], d); | ||
@@ -47,3 +49,5 @@ }, new Map()); | ||
var real = dep[_constants.KEY] || dep; | ||
return replacementMap.get(real) || dep; | ||
var replacedDep = replacementMap.get(real); | ||
_stats.stats.track(replacedDep, dep); | ||
return replacedDep || dep; | ||
}); | ||
@@ -50,0 +54,0 @@ } |
export { di } from './react/consumer'; | ||
export { runWithDi } from './react/global'; | ||
export { DiProvider, withDi } from './react/provider'; | ||
export { mock, injectable } from './react/utils'; | ||
export { mock, injectable } from './react/utils'; | ||
export { stats } from './react/stats'; |
import { PACKAGE_NAME } from './constants'; | ||
import { Context } from './context'; | ||
import { globalDi } from './global'; | ||
import { warnOnce, mock } from './utils'; | ||
@@ -9,3 +10,3 @@ function di(deps, target) { | ||
const { | ||
getDependencies = v => v | ||
getDependencies | ||
} = | ||
@@ -15,3 +16,3 @@ // grab value from alt renderer (eg react-test-renderer) | ||
// grab value from default renderer | ||
Context._currentValue || {}; | ||
Context._currentValue || globalDi; | ||
return getDependencies(deps, target); | ||
@@ -18,0 +19,0 @@ } else { |
import React from 'react'; | ||
import { globalDi } from './global'; | ||
export const Context = /*#__PURE__*/React.createContext({ | ||
getDependencies(deps) { | ||
return deps; | ||
} | ||
getDependencies: globalDi.getDependencies | ||
}); |
@@ -6,2 +6,3 @@ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } | ||
import { Context } from './context'; | ||
import { stats } from './stats'; | ||
import { getDisplayName } from './utils'; | ||
@@ -21,3 +22,6 @@ export const DiProvider = _ref => { | ||
// create a map of dependency real -> replacement for fast lookup | ||
const replacementMap = use.reduce((m, d) => m.set(d[KEY], d), new Map()); | ||
const replacementMap = use.reduce((m, d) => { | ||
stats.set(d); | ||
return m.set(d[KEY], d); | ||
}, new Map()); | ||
// support single or multiple targets | ||
@@ -37,3 +41,5 @@ const targets = target && (Array.isArray(target) ? target : [target]); | ||
const real = dep[KEY] || dep; | ||
return replacementMap.get(real) || dep; | ||
const replacedDep = replacementMap.get(real); | ||
stats.track(replacedDep, dep); | ||
return replacedDep || dep; | ||
}); | ||
@@ -40,0 +46,0 @@ } |
{ | ||
"name": "react-magnetic-di", | ||
"version": "2.2.13", | ||
"version": "2.3.0", | ||
"description": "Context driven dependency injection", | ||
@@ -49,31 +49,31 @@ "keywords": [ | ||
"devDependencies": { | ||
"@babel/cli": "^7.19.3", | ||
"@babel/core": "^7.20.2", | ||
"@babel/eslint-parser": "^7.19.1", | ||
"@babel/cli": "^7.21.0", | ||
"@babel/core": "^7.21.4", | ||
"@babel/eslint-parser": "^7.21.3", | ||
"@babel/plugin-proposal-class-properties": "^7.18.6", | ||
"@babel/plugin-transform-runtime": "^7.19.6", | ||
"@babel/preset-env": "^7.20.2", | ||
"@babel/preset-flow": "^7.18.6", | ||
"@babel/plugin-transform-runtime": "^7.21.4", | ||
"@babel/preset-env": "^7.21.4", | ||
"@babel/preset-flow": "^7.21.4", | ||
"@babel/preset-react": "^7.18.6", | ||
"@babel/runtime": "^7.20.1", | ||
"@testing-library/react": "^13.4.0", | ||
"@types/jest": "^29.2.3", | ||
"@types/react": "^18.0.25", | ||
"@types/react-dom": "^18.0.9", | ||
"babel-jest": "^29.3.1", | ||
"babel-loader": "^8.2.5", | ||
"@babel/runtime": "^7.21.0", | ||
"@testing-library/react": "^14.0.0", | ||
"@types/jest": "^29.5.1", | ||
"@types/react": "^18.2.0", | ||
"@types/react-dom": "^18.2.1", | ||
"babel-jest": "^29.5.0", | ||
"babel-loader": "^9.1.2", | ||
"babel-plugin-macros": "^3.1.0", | ||
"babel-plugin-module-resolver": "^4.1.0", | ||
"babel-plugin-module-resolver": "^5.0.0", | ||
"dtslint": "^4.2.1", | ||
"eslint": "^8.27.0", | ||
"eslint": "^8.39.0", | ||
"eslint-plugin-flowtype": "^8.0.3", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-local": "^1.0.0", | ||
"eslint-plugin-react": "^7.31.10", | ||
"eslint-plugin-react": "^7.32.2", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"flow-bin": "^0.169.0", | ||
"flow-copy-source": "^2.0.9", | ||
"jest": "^29.3.1", | ||
"jest-environment-jsdom": "^29.3.1", | ||
"prettier": "^2.7.1", | ||
"jest": "^29.5.0", | ||
"jest-environment-jsdom": "^29.5.0", | ||
"prettier": "^2.8.8", | ||
"prop-types": "^15.8.1", | ||
@@ -83,6 +83,6 @@ "react": "^18.2.0", | ||
"react-test-renderer": "^18.2.0", | ||
"typescript": "^4.9.4", | ||
"webpack": "^4.46.0", | ||
"webpack-cli": "^3.3.12", | ||
"webpack-dev-server": "^3.11.3" | ||
"typescript": "^5.0.4", | ||
"webpack": "^5.81.0", | ||
"webpack-cli": "^5.0.2", | ||
"webpack-dev-server": "^4.13.3" | ||
}, | ||
@@ -89,0 +89,0 @@ "engines": { |
<p align="center"> | ||
<img src="https://user-images.githubusercontent.com/84136/83958267-1c8f7f00-a8b3-11ea-9725-1d3530af5f8d.png" alt="react-magnetic-di logo" height="150" /> | ||
<img src="https://user-images.githubusercontent.com/84136/83958267-1c8f7f00-a8b3-11ea-9725-1d3530af5f8d.png" alt="magnetic-di logo" height="150" /> | ||
</p> | ||
<h1 align="center">react-magnetic-di</h1> | ||
<h1 align="center">magnetic-di</h1> | ||
<p align="center"> | ||
@@ -13,15 +13,15 @@ <a href="https://www.npmjs.com/package/react-magnetic-di"><img src="https://img.shields.io/npm/v/react-magnetic-di.svg"></a> | ||
A new take for dependency injection / dependency replacement in React for your tests, storybooks and even experiments in production. | ||
A new take for dependency injection / dependency replacement for your tests, storybooks and even experiments in production. | ||
- Close-to-zero performance overhead on dev/testing | ||
- **Zero** performance overhead on production (code gets stripped unless told otherwise) | ||
- Works with any kind of functions/classes (not only components) and in both class and functional components | ||
- Replaces dependencies at any depth of the React tree | ||
- Allows selective injection | ||
- Works with any kind of functions/classes (not only React components) and in both class and functional React components | ||
- Replaces dependencies at any depth of the React tree / call chain | ||
- Allows selective injection (React only) | ||
- Enforces separation of concerns, keeps your component API clean | ||
- Just uses Context, it does not mess up with React internals or modules/require | ||
- Just uses smart variable assignments, it does not mess up with React internals or modules/require | ||
## Philosophy | ||
Dependency injection and component injection is not a new topic. Especially the ability to provide a custom implementation of a component/hook while testing or writing storybooks and examples it is extremely valuable. `react-magnetic-di` takes inspiration from decorators, and with a touch of Babel magic and React Context allows you to optionally override "marked" dependencies inside your components so you can swap implementations only when needed. | ||
Dependency injection and component injection is not a new topic. Especially the ability to provide a custom implementation of a component/hook while testing or writing storybooks and examples it is extremely valuable. `react-magnetic-di` takes inspiration from decorators, and with a touch of Babel magic and React Context / globals allows you to optionally override "marked" dependencies inside your components so you can swap implementations only when needed. | ||
@@ -129,3 +129,3 @@ ## Usage | ||
In the example above we replace all `Modal` and `useQuery` dependencies across all components in the tree with the custom versions. | ||
In the example above we replace all `Modal` and `useQuery` dependencies across all components in the tree with the custom versions. | ||
If you want to replace dependencies **only** for a specific component (or set of components) you can use the `target` prop: | ||
@@ -163,5 +163,64 @@ | ||
When you have the same dependency replaced multiple times, there are two behaviours that determine which injectable will "win": | ||
- the one defined on the closest `DiProvider` wins. So you can declare more specific replacements by wrapping components with `DiProvider` or `withDi` and those will win over same type injectables on other top level `DiProvider`s | ||
- the injectable defined last in the `use` array wins. So you can define common injectables but still override each type case by case (eg: `<DiProvider use={[...commonDeps, specificInjectable]}>` | ||
### Using injection replacement outside of React | ||
The usage outside React is not much different, aside from the different way of clearing the replacements. | ||
```js | ||
import { fetchApi } from './fetch'; | ||
export async function myApiFetcher() { | ||
// "mark" any type of function/class as injectable | ||
di(fetchApi); | ||
const { data } = await fetchApi(); | ||
return data; | ||
} | ||
``` | ||
In the tests, you can use `runWithDi`, which will setup and clear the replacements for you after function execution is terminated. Such util also handles async code, but might require you to wrap the entire test to work effectively with scheduled code paths, or event driven implementations. | ||
```js | ||
import { injectable, runWithDi } from 'react-magnetic-di'; | ||
import { myApiFetcher, fetchApi } from '.'; | ||
it('should call the API', async () => { | ||
const fetchApiDi = injectable( | ||
fetchApi, | ||
jest.mock().mockResolvedValue('mock') | ||
); | ||
const result = await runWithDi(() => myApiFetcher(), [fetchApiDi]); | ||
expect(fetchApiDi).toHaveBeenCalled(); | ||
expect(result).toEqual('mock'); | ||
}); | ||
``` | ||
### Tracking unused injectables | ||
By default `magnetic-di` does not complain if an injectable is not used or if a dependency has not being replaced. In large codebases however, that might led to issues with stale, unused injectables or with lack of knowledge in what could be replaced. To ease introspection, the library provides a `stats` API that returns `unused` injectables. | ||
- `stats.unused()` returns an array of entries `{ get(), error() }` for all injectables that have not been used since `stats.reset()` has been called | ||
This is an example of stats guard implementation using the returned `error()` helper: | ||
```js | ||
import { stats } from 'react-magnetic-di'; | ||
beforeEach(() => { | ||
// it's important to reset the stats after each test | ||
stats.reset(); | ||
}); | ||
afterEach(() => { | ||
stats.unused().forEach((entry) => { | ||
// throw an error pointing at the test with the unused injectable | ||
throw entry.error(); | ||
}); | ||
}); | ||
``` | ||
### Configuration Options | ||
@@ -184,3 +243,2 @@ | ||
## ESLint plugin and rules | ||
@@ -227,3 +285,2 @@ | ||
## Contributing | ||
@@ -230,0 +287,0 @@ |
declare module 'react-magnetic-di' { | ||
import { | ||
ComponentType, | ||
ReactNode, | ||
Component, | ||
ComponentProps, | ||
ComponentClass, | ||
} from 'react'; | ||
import { ComponentType, ReactNode, Component, ComponentProps } from 'react'; | ||
@@ -65,2 +59,16 @@ type Dependency = Function; | ||
function runWithDi<T extends () => any>( | ||
thunk: T, | ||
dependencies: Dependency[] | ||
): ReturnType<T>; | ||
const stats: { | ||
/** Returns unused injectables */ | ||
unused(): Array<{ get(): Dependency; error(): Error }>; | ||
/** Returns dependencies missing an injectable override */ | ||
missing(): Array<{ get(): Dependency; error(): Error }>; | ||
/** Resets stats */ | ||
reset(): void; | ||
}; | ||
class di { | ||
@@ -67,0 +75,0 @@ /** @deprecated use injectable instead */ |
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
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
100297
53
2082
285