react-magnetic-di
Advanced tools
Comparing version 2.0.0 to 2.0.1
@@ -5,2 +5,14 @@ "use strict"; | ||
var PACKAGE_FUNCTION = 'di'; | ||
var ENABLED_ENVS = ['development', 'test']; | ||
var getComponentDeclaration = function getComponentDeclaration(t, scope) { | ||
// function declarations | ||
if (scope.parentBlock.declaration) return scope.parentBlock.declaration.id; | ||
if (scope.getBlockParent().block.id) return scope.getBlockParent().block.id; // variable declaration | ||
if (scope.parentBlock.id) return scope.parentBlock.id; // class declarations | ||
if (scope.parentBlock.type.includes('Class')) return scope.parent.block.id; | ||
}; | ||
var assert = { | ||
@@ -10,4 +22,4 @@ isValidBlock: function isValidBlock(t, ref) { | ||
if (!t.isFunctionDeclaration(block) && !t.isArrowFunctionExpression(block) && !t.isClassMethod(block)) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call. Must be inside a render function of a component. '); | ||
if (!t.isFunctionDeclaration(block) && !t.isFunctionExpression(block) && !t.isArrowFunctionExpression(block) && !t.isClassMethod(block)) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call: must be inside a render function of a component. '); | ||
} | ||
@@ -17,3 +29,3 @@ }, | ||
if (!ref.container.arguments.length) { | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments. Must be called with at least one argument. '); | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments: must be called with at least one argument. '); | ||
} | ||
@@ -24,4 +36,12 @@ | ||
})) { | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments. Must be called with plain identifiers. '); | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments: must be called with plain identifiers. '); | ||
} | ||
var decl = getComponentDeclaration(t, ref.scope); | ||
if (decl && ref.container.arguments.some(function (v) { | ||
return v.name === decl.name; | ||
})) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call: cannot inject self.'); | ||
} | ||
} | ||
@@ -32,5 +52,8 @@ }; | ||
var t = babel.types; | ||
var isEnabledEnv = babel.env(ENABLED_ENVS); | ||
return { | ||
visitor: { | ||
ImportDeclaration: function ImportDeclaration(path) { | ||
ImportDeclaration: function ImportDeclaration(path, _ref) { | ||
var _ref$opts = _ref.opts, | ||
opts = _ref$opts === void 0 ? {} : _ref$opts; | ||
// first we look at the imports: | ||
@@ -49,3 +72,4 @@ // if not our package and not the right function, ignore | ||
return t.isCallExpression(ref.container); | ||
}); // for each of that location we apply a tranformation | ||
}); | ||
var isEnabled = isEnabledEnv || Boolean(opts.forceEnable); // for each of that location we apply a tranformation | ||
@@ -60,9 +84,15 @@ references.forEach(function (ref) { | ||
}); | ||
var statement = ref.getStatementParent(); // generating variable declarations with array destructuring | ||
var statement = ref.getStatementParent(); // if should not be enabled, just remove the statement and exit | ||
if (!isEnabled) { | ||
statement.remove(); | ||
return; | ||
} // generating variable declarations with array destructuring | ||
// assigning them the result of the method call, with arguments | ||
// now wrapped in an array | ||
ref.scope.push({ | ||
id: t.arrayPattern(dependencyIdentifiers), | ||
init: t.callExpression(ref.node, [t.arrayExpression(args)]) | ||
init: t.callExpression(ref.node, [t.arrayExpression(args), getComponentDeclaration(t, ref.scope) || t.nullLiteral()]) | ||
}); | ||
@@ -69,0 +99,0 @@ args.forEach(function (argIdentifier) { |
@@ -12,3 +12,3 @@ "use strict"; | ||
function di(deps) { | ||
function di(deps, target) { | ||
// check if babel plugin has been added | ||
@@ -24,5 +24,5 @@ if (Array.isArray(deps)) { | ||
return getDependencies(deps); | ||
return getDependencies(deps, target); | ||
} else { | ||
(0, _utils.warnOnce)("Seems like you are using react-magnetic-di without babel plugin. " + "Please add 'react-magnetic-di/babel' to your babel config to enabled dependency injection. " + 'Without that, di(...) is a no-op.'); | ||
(0, _utils.warnOnce)("Seems like you are using react-magnetic-di without Babel plugin. " + "Please add 'react-magnetic-di/babel' to your Babel config to enabled dependency injection. " + 'Without such plugin di(...) is a no-op.'); | ||
} | ||
@@ -29,0 +29,0 @@ } |
"use strict"; | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
Object.defineProperty(exports, "__esModule", { | ||
@@ -17,2 +19,4 @@ value: true | ||
var _utils = require("./utils"); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } | ||
@@ -24,92 +28,57 @@ | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
var DiProvider = function DiProvider(_ref) { | ||
var children = _ref.children, | ||
use = _ref.use, | ||
target = _ref.target; | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var _useContext = (0, _react.useContext)(_context.Context), | ||
_getDependencies = _useContext.getDependencies; // memo provider value so gets computed only once | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
var value = (0, _react.useMemo)(function () { | ||
// create a map of dependency real -> mock components for fast lookup | ||
var useMap = use.reduce(function (m, d) { | ||
return m.set(d[_constants.KEY], d); | ||
}, new Map()); // support single or multiple targets | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } | ||
var targets = target && (Array.isArray(target) ? target : [target]); | ||
return { | ||
getDependencies: function getDependencies(realDeps, targetChild) { | ||
// First we collect dependencies from parent providers (if any) | ||
// If a dependency is not defined we return the original | ||
var dependencies = _getDependencies(realDeps, targetChild); | ||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } | ||
if (!targetChild || !targets || targets.includes(targetChild)) { | ||
return dependencies.map(function (dep) { | ||
return useMap.get(dep) || dep; | ||
}); | ||
} | ||
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } | ||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } | ||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } | ||
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } | ||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
var DiProvider = /*#__PURE__*/function (_Component) { | ||
_inherits(DiProvider, _Component); | ||
var _super = _createSuper(DiProvider); | ||
function DiProvider(props) { | ||
var _this; | ||
_classCallCheck(this, DiProvider); | ||
_this = _super.call(this, props); | ||
_defineProperty(_assertThisInitialized(_this), "getDependencies", function (realDeps) { | ||
var _assertThisInitialize = _assertThisInitialized(_this), | ||
useMap = _assertThisInitialize.useMap; // First we collect dependencies from parent providers (if any) | ||
// If a dependency is not defined we return the original | ||
var dependencies = _this.context.getDependencies(realDeps); | ||
return dependencies.map(function (dep) { | ||
return useMap.get(dep) || dep; | ||
}); | ||
}); | ||
_this.useMap = new Map(); | ||
props.use.forEach(function (d) { | ||
return _this.useMap.set(d[_constants.KEY], d); | ||
}); | ||
_this.state = { | ||
getDependencies: _this.getDependencies | ||
return dependencies; | ||
} | ||
}; | ||
return _this; | ||
} | ||
}, [_getDependencies]); // ignore use & target props | ||
_createClass(DiProvider, [{ | ||
key: "render", | ||
value: function render() { | ||
var children = this.props.children; | ||
return /*#__PURE__*/_react["default"].createElement(_context.Context.Provider, { | ||
value: this.state | ||
}, children); | ||
} | ||
}]); | ||
return /*#__PURE__*/_react["default"].createElement(_context.Context.Provider, { | ||
value: value | ||
}, children); | ||
}; | ||
return DiProvider; | ||
}(_react.Component); | ||
exports.DiProvider = DiProvider; | ||
DiProvider.propTypes = { | ||
children: _propTypes["default"].oneOfType([_propTypes["default"].func, _propTypes["default"].node]), | ||
target: _propTypes["default"].oneOfType([_propTypes["default"].func, _propTypes["default"].arrayOf(_propTypes["default"].func)]), | ||
use: _propTypes["default"].arrayOf(_propTypes["default"].func).isRequired | ||
}; | ||
_defineProperty(DiProvider, "contextType", _context.Context); | ||
_defineProperty(DiProvider, "propTypes", { | ||
use: _propTypes["default"].arrayOf(_propTypes["default"].func).isRequired, | ||
children: _propTypes["default"].oneOfType([_propTypes["default"].func, _propTypes["default"].node]) | ||
}); | ||
function withDi(Comp, deps) { | ||
function withDi(Comp, deps, target) { | ||
var WrappedComponent = function WrappedComponent() { | ||
return /*#__PURE__*/_react["default"].createElement(DiProvider, { | ||
use: deps | ||
use: deps, | ||
target: target | ||
}, /*#__PURE__*/_react["default"].createElement(Comp, null)); | ||
}; | ||
WrappedComponent.displayName = "withDi(".concat(Comp.displayName || '', ")"); | ||
WrappedComponent.displayName = "withDi(".concat((0, _utils.getDisplayName)(Comp), ")"); | ||
return WrappedComponent; | ||
} |
@@ -6,4 +6,5 @@ "use strict"; | ||
}); | ||
exports.warnOnce = warnOnce; | ||
exports.getDisplayName = getDisplayName; | ||
exports.mock = mock; | ||
exports.warnOnce = void 0; | ||
@@ -14,3 +15,3 @@ var _constants = require("./constants"); | ||
var warnOnce = function warnOnce(message) { | ||
function warnOnce(message) { | ||
if (!hasWarned) { | ||
@@ -21,10 +22,12 @@ // eslint-disable-next-line no-console | ||
} | ||
}; | ||
} | ||
exports.warnOnce = warnOnce; | ||
function getDisplayName(Comp) { | ||
return Comp.displayName || Comp.name || 'Unknown'; | ||
} | ||
function mock(original, mockImpl) { | ||
mockImpl.displayName = mockImpl.displayName || "di(".concat(original.displayName || original.name, ")"); | ||
mockImpl.displayName = mockImpl.displayName || "di(".concat(getDisplayName(original), ")"); | ||
mockImpl[_constants.KEY] = original; | ||
return mockImpl; | ||
} |
var PACKAGE_NAME = 'react-magnetic-di'; | ||
var PACKAGE_FUNCTION = 'di'; | ||
var ENABLED_ENVS = ['development', 'test']; | ||
var getComponentDeclaration = function getComponentDeclaration(t, scope) { | ||
// function declarations | ||
if (scope.parentBlock.declaration) return scope.parentBlock.declaration.id; | ||
if (scope.getBlockParent().block.id) return scope.getBlockParent().block.id; // variable declaration | ||
if (scope.parentBlock.id) return scope.parentBlock.id; // class declarations | ||
if (scope.parentBlock.type.includes('Class')) return scope.parent.block.id; | ||
}; | ||
var assert = { | ||
@@ -7,4 +19,4 @@ isValidBlock: function isValidBlock(t, ref) { | ||
if (!t.isFunctionDeclaration(block) && !t.isArrowFunctionExpression(block) && !t.isClassMethod(block)) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call. Must be inside a render function of a component. '); | ||
if (!t.isFunctionDeclaration(block) && !t.isFunctionExpression(block) && !t.isArrowFunctionExpression(block) && !t.isClassMethod(block)) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call: must be inside a render function of a component. '); | ||
} | ||
@@ -14,3 +26,3 @@ }, | ||
if (!ref.container.arguments.length) { | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments. Must be called with at least one argument. '); | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments: must be called with at least one argument. '); | ||
} | ||
@@ -21,4 +33,12 @@ | ||
})) { | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments. Must be called with plain identifiers. '); | ||
throw ref.buildCodeFrameError('Invalid di(...) arguments: must be called with plain identifiers. '); | ||
} | ||
var decl = getComponentDeclaration(t, ref.scope); | ||
if (decl && ref.container.arguments.some(function (v) { | ||
return v.name === decl.name; | ||
})) { | ||
throw ref.buildCodeFrameError('Invalid di(...) call: cannot inject self.'); | ||
} | ||
} | ||
@@ -29,5 +49,8 @@ }; | ||
var t = babel.types; | ||
var isEnabledEnv = babel.env(ENABLED_ENVS); | ||
return { | ||
visitor: { | ||
ImportDeclaration: function ImportDeclaration(path) { | ||
ImportDeclaration: function ImportDeclaration(path, _ref) { | ||
var _ref$opts = _ref.opts, | ||
opts = _ref$opts === void 0 ? {} : _ref$opts; | ||
// first we look at the imports: | ||
@@ -46,3 +69,4 @@ // if not our package and not the right function, ignore | ||
return t.isCallExpression(ref.container); | ||
}); // for each of that location we apply a tranformation | ||
}); | ||
var isEnabled = isEnabledEnv || Boolean(opts.forceEnable); // for each of that location we apply a tranformation | ||
@@ -57,9 +81,15 @@ references.forEach(function (ref) { | ||
}); | ||
var statement = ref.getStatementParent(); // generating variable declarations with array destructuring | ||
var statement = ref.getStatementParent(); // if should not be enabled, just remove the statement and exit | ||
if (!isEnabled) { | ||
statement.remove(); | ||
return; | ||
} // generating variable declarations with array destructuring | ||
// assigning them the result of the method call, with arguments | ||
// now wrapped in an array | ||
ref.scope.push({ | ||
id: t.arrayPattern(dependencyIdentifiers), | ||
init: t.callExpression(ref.node, [t.arrayExpression(args)]) | ||
init: t.callExpression(ref.node, [t.arrayExpression(args), getComponentDeclaration(t, ref.scope) || t.nullLiteral()]) | ||
}); | ||
@@ -66,0 +96,0 @@ args.forEach(function (argIdentifier) { |
import { Context } from './context'; | ||
import { warnOnce, mock } from './utils'; | ||
function di(deps) { | ||
function di(deps, target) { | ||
// check if babel plugin has been added | ||
@@ -15,5 +15,5 @@ if (Array.isArray(deps)) { | ||
return getDependencies(deps); | ||
return getDependencies(deps, target); | ||
} else { | ||
warnOnce("Seems like you are using react-magnetic-di without babel plugin. " + "Please add 'react-magnetic-di/babel' to your babel config to enabled dependency injection. " + 'Without that, di(...) is a no-op.'); | ||
warnOnce("Seems like you are using react-magnetic-di without Babel plugin. " + "Please add 'react-magnetic-di/babel' to your Babel config to enabled dependency injection. " + 'Without such plugin di(...) is a no-op.'); | ||
} | ||
@@ -20,0 +20,0 @@ } |
@@ -1,93 +0,58 @@ | ||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } | ||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } | ||
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } | ||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } | ||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } | ||
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } | ||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
import React, { Component } from 'react'; | ||
import React, { useContext, useMemo } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { KEY } from './constants'; | ||
import { Context } from './context'; | ||
export var DiProvider = /*#__PURE__*/function (_Component) { | ||
_inherits(DiProvider, _Component); | ||
import { getDisplayName } from './utils'; | ||
export var DiProvider = function DiProvider(_ref) { | ||
var children = _ref.children, | ||
use = _ref.use, | ||
target = _ref.target; | ||
var _super = _createSuper(DiProvider); | ||
var _useContext = useContext(Context), | ||
_getDependencies = _useContext.getDependencies; // memo provider value so gets computed only once | ||
function DiProvider(props) { | ||
var _this; | ||
_classCallCheck(this, DiProvider); | ||
var value = useMemo(function () { | ||
// create a map of dependency real -> mock components for fast lookup | ||
var useMap = use.reduce(function (m, d) { | ||
return m.set(d[KEY], d); | ||
}, new Map()); // support single or multiple targets | ||
_this = _super.call(this, props); | ||
var targets = target && (Array.isArray(target) ? target : [target]); | ||
return { | ||
getDependencies: function getDependencies(realDeps, targetChild) { | ||
// First we collect dependencies from parent providers (if any) | ||
// If a dependency is not defined we return the original | ||
var dependencies = _getDependencies(realDeps, targetChild); | ||
_defineProperty(_assertThisInitialized(_this), "getDependencies", function (realDeps) { | ||
var _assertThisInitialize = _assertThisInitialized(_this), | ||
useMap = _assertThisInitialize.useMap; // First we collect dependencies from parent providers (if any) | ||
// If a dependency is not defined we return the original | ||
if (!targetChild || !targets || targets.includes(targetChild)) { | ||
return dependencies.map(function (dep) { | ||
return useMap.get(dep) || dep; | ||
}); | ||
} | ||
var dependencies = _this.context.getDependencies(realDeps); | ||
return dependencies.map(function (dep) { | ||
return useMap.get(dep) || dep; | ||
}); | ||
}); | ||
_this.useMap = new Map(); | ||
props.use.forEach(function (d) { | ||
return _this.useMap.set(d[KEY], d); | ||
}); | ||
_this.state = { | ||
getDependencies: _this.getDependencies | ||
return dependencies; | ||
} | ||
}; | ||
return _this; | ||
} | ||
}, [_getDependencies]); // ignore use & target props | ||
_createClass(DiProvider, [{ | ||
key: "render", | ||
value: function render() { | ||
var children = this.props.children; | ||
return /*#__PURE__*/React.createElement(Context.Provider, { | ||
value: this.state | ||
}, children); | ||
} | ||
}]); | ||
return DiProvider; | ||
}(Component); | ||
_defineProperty(DiProvider, "contextType", Context); | ||
_defineProperty(DiProvider, "propTypes", { | ||
use: PropTypes.arrayOf(PropTypes.func).isRequired, | ||
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]) | ||
}); | ||
export function withDi(Comp, deps) { | ||
return /*#__PURE__*/React.createElement(Context.Provider, { | ||
value: value | ||
}, children); | ||
}; | ||
DiProvider.propTypes = { | ||
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), | ||
target: PropTypes.oneOfType([PropTypes.func, PropTypes.arrayOf(PropTypes.func)]), | ||
use: PropTypes.arrayOf(PropTypes.func).isRequired | ||
}; | ||
export function withDi(Comp, deps, target) { | ||
var WrappedComponent = function WrappedComponent() { | ||
return /*#__PURE__*/React.createElement(DiProvider, { | ||
use: deps | ||
use: deps, | ||
target: target | ||
}, /*#__PURE__*/React.createElement(Comp, null)); | ||
}; | ||
WrappedComponent.displayName = "withDi(".concat(Comp.displayName || '', ")"); | ||
WrappedComponent.displayName = "withDi(".concat(getDisplayName(Comp), ")"); | ||
return WrappedComponent; | ||
} |
import { KEY } from './constants'; | ||
var hasWarned = false; | ||
export var warnOnce = function warnOnce(message) { | ||
export function warnOnce(message) { | ||
if (!hasWarned) { | ||
@@ -9,7 +9,10 @@ // eslint-disable-next-line no-console | ||
} | ||
}; | ||
} | ||
export function getDisplayName(Comp) { | ||
return Comp.displayName || Comp.name || 'Unknown'; | ||
} | ||
export function mock(original, mockImpl) { | ||
mockImpl.displayName = mockImpl.displayName || "di(".concat(original.displayName || original.name, ")"); | ||
mockImpl.displayName = mockImpl.displayName || "di(".concat(getDisplayName(original), ")"); | ||
mockImpl[KEY] = original; | ||
return mockImpl; | ||
} |
{ | ||
"name": "react-magnetic-di", | ||
"version": "2.0.0", | ||
"version": "2.0.1", | ||
"description": "Context driven dependency injection", | ||
@@ -27,8 +27,7 @@ "keywords": [ | ||
"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", | ||
"test": "jest", | ||
"flow": "flow --max-warnings=0", | ||
"types": "dtslint --expectOnly --localTs node_modules/typescript/lib ./types && tsc && flow --max-warnings=0", | ||
"lint": "eslint ./src", | ||
"preversion": "npm run lint -s && npm run flow -s && npm run test -s", | ||
"preversion": "npm run lint -s && npm run types -s && npm run test -s", | ||
"prepack": "npm run preversion -s && npm run build -s", | ||
@@ -43,20 +42,24 @@ "start": "webpack-dev-server" | ||
"devDependencies": { | ||
"@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/cli": "^7.10.1", | ||
"@babel/core": "^7.10.2", | ||
"@babel/plugin-proposal-class-properties": "^7.10.1", | ||
"@babel/plugin-transform-runtime": "^7.10.1", | ||
"@babel/preset-env": "^7.10.2", | ||
"@babel/preset-flow": "^7.10.1", | ||
"@babel/preset-react": "^7.10.1", | ||
"@babel/runtime": "^7.10.2", | ||
"@testing-library/react": "^10.2.1", | ||
"@types/react": "^16.9.35", | ||
"@types/react-dom": "^16.9.8", | ||
"babel-eslint": "^10.1.0", | ||
"babel-jest": "^26.0.1", | ||
"babel-loader": "^8.1.0", | ||
"dtslint": "^3.6.10", | ||
"enzyme": "~3.11.0", | ||
"enzyme-adapter-react-16": "^1.15.0", | ||
"eslint": "^7.0.0", | ||
"eslint-plugin-flowtype": "^5.1.0", | ||
"eslint": "^7.2.0", | ||
"eslint-plugin-flowtype": "^5.1.3", | ||
"eslint-plugin-import": "^2.20.2", | ||
"eslint-plugin-react": "^7.20.0", | ||
"eslint-plugin-react-hooks": "^4.0.2", | ||
"eslint-plugin-react-hooks": "^4.0.4", | ||
"flow-bin": "^0.125.1", | ||
@@ -69,2 +72,3 @@ "flow-copy-source": "^2.0.9", | ||
"react-dom": "^16.13.1", | ||
"typescript": "^3.9.5", | ||
"webpack": "^4.43.0", | ||
@@ -71,0 +75,0 @@ "webpack-cli": "^3.3.11", |
@@ -1,17 +0,22 @@ | ||
# react-magnetic-di | ||
<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" /> | ||
</p> | ||
<h1 align="center">react-magnetic-di</h1> | ||
<p align="center"> | ||
<a href="https://www.npmjs.com/package/react-magnetic-di"><img src="https://img.shields.io/npm/v/react-magnetic-di.svg"></a> | ||
<a href="https://bundlephobia.com/result?p=react-magnetic-di"><img src="https://img.shields.io/bundlephobia/minzip/react-magnetic-di.svg" /></a> | ||
<a href="https://codecov.io/gh/albertogasparin/react-magnetic-di"><img src="https://codecov.io/gh/albertogasparin/react-magnetic-di/branch/master/graph/badge.svg" /> | ||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg"></a> | ||
<!--a href="CONTRIBUTING"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" /></a--> | ||
</p> | ||
[![npm](https://img.shields.io/npm/v/react-magnetic-di.svg)](https://www.npmjs.com/package/react-magnetic-di) | ||
[![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/react-magnetic-di.svg)](https://bundlephobia.com/result?p=react-magnetic-di) | ||
[![License](https://img.shields.io/:license-MIT-blue.svg)](http://albertogasparin.mit-license.org) | ||
[![CircleCI](https://circleci.com/gh/albertogasparin/react-magnetic-di.svg?style=shield&circle-token=cc7bd7e07aae2bb3fcde0a2bfb148b5c2208af84)](https://circleci.com/gh/albertogasparin/react-magnetic-di) | ||
[![codecov](https://codecov.io/gh/albertogasparin/react-magnetic-di/branch/master/graph/badge.svg)](https://codecov.io/gh/albertogasparin/react-magnetic-di) | ||
A new take for dependency injection in React for your tests, storybooks and even experiments in production. | ||
- Close-to-zero performance overhead | ||
- 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 across the entire tree | ||
- Replaces dependencies at any depth of the React tree | ||
- Allows selective injection | ||
- Enforces separation of concerns | ||
- Uses Context in light way (not messing up with React internals) | ||
- Enforces separation of concerns, keeps your component API clean | ||
- Just uses Context, it does not mess up with React internals or require | ||
@@ -22,5 +27,5 @@ ## 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, like leaking internal implementation details, mixing together injected dependencies with other props and introducing additional complexity when typing props. | ||
A common pattern to solve this problem is injecting those "dependencies" via props or using mocking libraries at import/require level. Those approaches however have some of downsides, like leaking internal implementation details into the component's public API, being quite fragile or introducing additional typing complexity. | ||
`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). | ||
`react-magnetic-di` takes inspiration from decorators, and with a touch of Babel magic and React Context allows you to optionally override such dependencies, with nearly-zero performance overhead while developing/testing (it's basically a function call and a map lookup) and it is fully removed (by default) on production builds. | ||
@@ -37,3 +42,3 @@ ## Usage | ||
Edit your Babel config file (`.babel.rc` / `babel.config.js` / ...) and add: | ||
Edit your Babel config file (`.babelrc` / `babel.config.js` / ...) and add: | ||
@@ -44,3 +49,3 @@ ```js | ||
// ... other plugins | ||
'react-magnetic-di/babel', | ||
'react-magnetic-di/babel-plugin', | ||
], | ||
@@ -103,3 +108,4 @@ ``` | ||
// and the replacement implementation as second | ||
const ModalOpen = di.mock(Modal, () => <div />); | ||
// (you can also import { mock } if don't like di prefix) | ||
const ModalOpenMock = di.mock(Modal, () => <div />); | ||
const useQueryMock = di.mock(useQuery, () => ({ data: null })); | ||
@@ -111,5 +117,5 @@ | ||
wrappingComponent: DiProvider, | ||
wrappingComponentProps: { use: [ModalOpen, useQuery] }, | ||
wrappingComponentProps: { use: [ModalOpenMock, useQueryMock] }, | ||
}); | ||
expect(container).toMatchSnapshot(); | ||
expect(container.html()).toMatchSnapshot(); | ||
}); | ||
@@ -120,3 +126,3 @@ | ||
const { container } = render(<MyComponent />, { | ||
wrapper: (p) => <DiProvider use={[ModalOpen, useQuery]} {...p} />, | ||
wrapper: (p) => <DiProvider use={[ModalOpenMock, useQueryMock]} {...p} />, | ||
}); | ||
@@ -128,3 +134,3 @@ expect(container).toMatchSnapshot(); | ||
storiesOf('Modal content', module).add('with text', () => ( | ||
<DiProvider use={[ModalOpen, useQuery]}> | ||
<DiProvider use={[ModalOpenMock, useQueryMock]}> | ||
<MyComponent /> | ||
@@ -135,8 +141,38 @@ </DiProvider> | ||
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: | ||
## FAQ | ||
```jsx | ||
// story.js | ||
storiesOf('Modal content', module).add('with text', () => ( | ||
<DiProvider target={[MyComponent, MyOtherComponent]} use={[ModalOpen]}> | ||
<DiProvider target={MyComponent} use={[useQuery]}> | ||
<MyComponent /> | ||
<MyOtherComponent> | ||
</DiProvider> | ||
</DiProvider> | ||
)); | ||
``` | ||
- Can I replace only one instance for one component? Currently no, but it could be possible. | ||
In the example above `MyComponent` will have both `ModalOpen` and `useQuery` replaced while `MyOtherComponent` only `ModalOpen`. Be aware that `target` needs an actual component declaration to work, so will not work in cases where the component is fully anonymous (eg: `export default () => ...` or `forwardRef(() => ...)`). | ||
### Configuration Options | ||
#### Enable depepndency injection on production (or custom env) | ||
By default dependency injection is enabled on `development` and `test` environments only, which means `di(...)` is removed on production builds. If you want to allow depepency injection on production too (or on a custom env) you can use the `forceEnable` option: | ||
``` | ||
// In your .babelrc / babel.config.js | ||
// ... other stuff like presets | ||
plugins: [ | ||
// ... other plugins | ||
['react-magnetic-di/babel-plugin', { forceEnable: true }], | ||
], | ||
``` | ||
## Current limitations | ||
- Does not support Enzyme shallow ([due to shallow not fully supporting context](https://github.com/enzymejs/enzyme/issues/2176)). If you wish to shallow anyway, you could mock `di` and manually return the array of mocked dependencies, but it is not recommended. | ||
- Does not support dynamic `use` and `target` props | ||
## Contributing | ||
@@ -143,0 +179,0 @@ |
@@ -19,3 +19,5 @@ declare module 'react-magnetic-di' { | ||
function di(...dependencies: Dependency[]): void; | ||
di.mock = mock; | ||
class di { | ||
static mock: typeof mock; | ||
} | ||
} |
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
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
22
440
174
0
32125
33