@bem-react/di
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -5,17 +5,28 @@ 'use strict'; | ||
var _assign = require('object-assign'); | ||
var React = require('react'); | ||
var __assign = function () { | ||
__assign = _assign || function __assign(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use | ||
this file except in compliance with the License. You may obtain a copy of the | ||
License at http://www.apache.org/licenses/LICENSE-2.0 | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; | ||
} | ||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED | ||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, | ||
MERCHANTABLITY OR NON-INFRINGEMENT. | ||
return t; | ||
}; | ||
See the Apache Version 2.0 License for specific language governing permissions | ||
and limitations under the License. | ||
***************************************************************************** */ | ||
return __assign.apply(this, arguments); | ||
var __assign = function() { | ||
__assign = Object.assign || function __assign(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
@@ -22,0 +33,0 @@ |
@@ -1,1 +0,1 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var _assign=require("object-assign"),React=require("react"),__assign=function(){return(__assign=_assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},registryContext=React.createContext({}),RegistryProvider=registryContext.Provider,RegistryConsumer=registryContext.Consumer;function withRegistry(){for(var i=[],e=0;e<arguments.length;e++)i[e]=arguments[e];return function(t){var e=function(e){return React.createElement(RegistryConsumer,null,function(r){var n={};return i.forEach(function(e){var t=r[e.id];n[e.id]=e.inverted?t||e:e||t}),React.createElement(RegistryProvider,{value:n},React.createElement(t,__assign({},e)))})};return e.displayName="RegistryResolver("+i.map(function(e){return e.id}).join(", ")+")",e}}var Registry=function(){function e(e){var t=e.id,r=e.inverted,n=void 0!==r&&r;this.components=new Map,this.id=t,this.inverted=n}return e.prototype.set=function(e,t){return this.components.set(e,t),this},e.prototype.get=function(e){return this.components.get(e)},e}();exports.RegistryConsumer=RegistryConsumer,exports.withRegistry=withRegistry,exports.Registry=Registry; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var React=require("react"),__assign=function(){return(__assign=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e}).apply(this,arguments)},registryContext=React.createContext({}),RegistryProvider=registryContext.Provider,RegistryConsumer=registryContext.Consumer;function withRegistry(){for(var i=[],e=0;e<arguments.length;e++)i[e]=arguments[e];return function(t){var e=function(e){return React.createElement(RegistryConsumer,null,function(r){var n={};return i.forEach(function(e){var t=r[e.id];n[e.id]=e.inverted?t||e:e||t}),React.createElement(RegistryProvider,{value:n},React.createElement(t,__assign({},e)))})};return e.displayName="RegistryResolver("+i.map(function(e){return e.id}).join(", ")+")",e}}var Registry=function(){function e(e){var t=e.id,r=e.inverted,n=void 0!==r&&r;this.components=new Map,this.id=t,this.inverted=n}return e.prototype.set=function(e,t){return this.components.set(e,t),this},e.prototype.get=function(e){return this.components.get(e)},e}();exports.RegistryConsumer=RegistryConsumer,exports.withRegistry=withRegistry,exports.Registry=Registry; |
@@ -6,2 +6,18 @@ # Change Log | ||
# 1.1.0 (2018-12-06) | ||
### Bug Fixes | ||
* **di:** use map as class option for using in es5 ([24e9015](https://github.com/bem/bem-react/tree/master/packages/di/commit/24e9015)) | ||
### Features | ||
* **v3:** init packages ([d652328](https://github.com/bem/bem-react/tree/master/packages/di/commit/d652328)) | ||
<a name="1.0.2"></a> | ||
@@ -8,0 +24,0 @@ ## [1.0.2](https://github.com/bem/bem-react-core/compare/@bem-react/di@1.0.1...@bem-react/di@1.0.2) (2018-10-24) |
import * as React from 'react'; | ||
export declare type RegistryContext = Record<string, Registry>; | ||
export declare const RegistryConsumer: React.ComponentType<React.ConsumerProps<Record<string, Registry>>>; | ||
export declare function withRegistry<P>(...registries: Registry[]): (Component: React.ComponentType<P>) => React.StatelessComponent<P>; | ||
export declare const RegistryConsumer: React.ExoticComponent<React.ConsumerProps<Record<string, Registry>>>; | ||
export declare function withRegistry<P>(...registries: Registry[]): (Component: React.ComponentType<P>) => React.FunctionComponent<P>; | ||
export interface IRegistryOptions { | ||
@@ -6,0 +6,0 @@ id: string; |
{ | ||
"name": "@bem-react/di", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "BEM React Dependency Injection", | ||
"repository": { | ||
"url": "git://github.com/bem/bem-react-core.git", | ||
"type": "git" | ||
}, | ||
"repository": "https://github.com/bem/bem-react/tree/master/packages/di", | ||
"keywords": [ | ||
"bem", | ||
"core" | ||
"level", | ||
"dependency", | ||
"di", | ||
"dependency injection", | ||
"react" | ||
], | ||
@@ -13,0 +14,0 @@ "main": "index.js", |
212
README.md
@@ -1,134 +0,123 @@ | ||
# DI | ||
# Dependency Injection (DI) | ||
DI (dependency injection) is very simple way to use any versions of components in any place of your App. | ||
**Dependency Injection (DI)** allows you to split React components into separate versions and comfortably switch them in the project whenever needed, e.g., to make a specific bundle. | ||
Let's see an example. | ||
DI package helps to solve similar tasks with minimum effort: | ||
- decouple *desktop* and *mobile* versions of a component | ||
- implement an *experimental* version of a component alongside the common one | ||
## Problem | ||
## Install | ||
``` | ||
npm i @bem-react/di -S | ||
``` | ||
You already wrote React-components for your BEM App: | ||
```tsx | ||
// components/Button/Button.tsx | ||
## Quick start | ||
import * as React from 'react'; | ||
import { cn } from '@bem-react/classname'; | ||
**Note!** This example uses [ClassName package](https://github.com/bem/bem-react/tree/master/packages/classname). | ||
const cnButton = cn('Button'); | ||
E.g., for a structure like this: | ||
``` | ||
Components/ | ||
Header/ | ||
Header@desktop.tsx | ||
Header@mobile.tsx | ||
Footer/ | ||
Footer@desktop.tsx | ||
Footer@mobile.tsx | ||
App.tsx | ||
``` | ||
export interface IButtonProps { | ||
text: string; | ||
} | ||
First, create two files that define two versions of the App and use different sets of components: `App@desktop.tsx` and `App@mobile.tsx`. Put them near `App.tsx`. | ||
export interface IButtonState { | ||
pointed: boolean; | ||
} | ||
export class Button extends React.Component<IButtonProps, IButtonState> { | ||
constructor() { | ||
this.state = { pointed: false }; | ||
} | ||
private onPointerEnter() { | ||
this.setState({ pointed: true }); | ||
} | ||
private onPointerLeave() { | ||
this.setState({ pointed: false }); | ||
} | ||
protected abstract attrs: () => React.DOMAttributes; | ||
render() { | ||
return ( | ||
<div className={cnButton(this.state)} {...this.attrs()}> | ||
{this.props.text} | ||
</div> | ||
); | ||
} | ||
} | ||
// components/App/App.tsx | ||
import * as React from 'react'; | ||
import { Button } from '../Button/Button'; | ||
export const App: React.SFC = () => <Button text="Hello there!" />; | ||
In each App version (`App@desktop.tsx` and `App@mobile.tsx`) we should define which components should be used. | ||
Three steps to do this: | ||
1. Create a registry with a particular id: | ||
```javascript | ||
const registry = new Registry({ id: cnApp() }); | ||
``` | ||
2. Register all the needed components versions under a descriptive key (keys, describing similar components, should be the same across all the versions): | ||
```javascript | ||
registry.set(cnHeader(), Header); | ||
registry.set(cnFooter(), Footer); | ||
``` | ||
3. Export the App version with its registry of components: | ||
```javascript | ||
export const AppNewVersion = withRegistry(registry)(AppCommon); | ||
``` | ||
At some point you realized that "Button" should behave differently on desktop and mobile platforms. | ||
The files should look like this: | ||
Two questions: | ||
**1.** In `App@desktop.tsx` | ||
```javascript | ||
import { cn } from '@bem-react/classname'; | ||
import { Registry, withRegistry } from '@bem-react/di'; | ||
import { App as AppCommon } from './App'; | ||
- **Q1.** How to describe two versions of "Button"? | ||
- **Q2.** How to do import specific version of "Button" with minimum effort? | ||
import { Footer } from './Components/Footer/Footer@desktop'; | ||
import { Header } from './Components/Header/Header@desktop'; | ||
## Solution | ||
const cnApp = cn('App'); | ||
const cnHeader = cn('Header'); | ||
const cnFooter = cn('Footer'); | ||
**A1.** Describing two versions of a component: | ||
const registry = new Registry({ id: cnApp() }); | ||
```tsx | ||
// components/Button/Button@desktop.tsx - desktop version | ||
registry.set(cnHeader(), Header); | ||
registry.set(cnFooter(), Footer); | ||
import * as React from 'react'; | ||
import { Button as ButtonCommon } from './Button'; | ||
export class Button extends ButtonCommon { | ||
protected attrs() { | ||
return { | ||
onMouseEnter: this.onPointerEnter, | ||
onMouseLeave: this.onPointerLeave, | ||
}; | ||
} | ||
} | ||
// components/Button/Button@mobile.tsx - mobile version | ||
import * as React from 'react'; | ||
import { Button as ButtonCommon } from './Button'; | ||
export class Button extends ButtonCommon { | ||
protected attrs() { | ||
return { | ||
onTouchMove: this.onPointerEnter, | ||
onTouchEnd: this.onPointerLeave, | ||
}; | ||
} | ||
} | ||
export const AppDesktop = withRegistry(registry)(AppCommon); | ||
``` | ||
**A2.** Importing components with "di": | ||
```tsx | ||
// components/App/App@desktop.tsx | ||
**2.** In `App@mobile.tsx` | ||
```javascript | ||
import { cn } from '@bem-react/classname'; | ||
import { Registry, withRegistry } from '@bem-react/di'; | ||
import { cn } from '@bem-react/classname'; | ||
import { App as AppCommon } from './App'; | ||
import { Button } from '../Button/Button@desktop'; | ||
import { Footer } from './Components/Footer/Footer@mobile'; | ||
import { Header } from './Components/Header/Header@mobile'; | ||
const cnApp = cn('App'); | ||
const cnButton = cn('Button'); | ||
const cnHeader = cn('Header'); | ||
const cnFooter = cn('Footer'); | ||
// registry with desktop versions of components | ||
const registry = new Registry({ id: cnApp() }); | ||
registry.set(cnButton(), Button); | ||
registry.set(cnHeader(), Header); | ||
registry.set(cnFooter(), Footer); | ||
export const App = withRegistry(registry)(AppCommon); | ||
export const AppMobile = withRegistry(registry)(AppCommon); | ||
``` | ||
// components/App/App@mobile.tsx | ||
// | ||
// ...very similar... | ||
// | ||
Time to use these versions in your app dynamically! | ||
If in `App.tsx` your dependencies were static before | ||
```javascript | ||
import { cn } from '@bem-react/classname'; | ||
import { Header } from './Components/Header/Header'; | ||
import { Footer } from './Components/Footer/Footer'; | ||
// components/App/App.tsx - with small changes | ||
const cnPage = cn('Page'); | ||
import * as React from 'react'; | ||
export const App: React.SFC = () => ( | ||
<div className={ cnPage() }> | ||
<Header /> | ||
<Content /> | ||
<Footer /> | ||
</div> | ||
); | ||
``` | ||
Now the dependencies can be injected based on the currently used registry | ||
```javascript | ||
import { cn } from '@bem-react/classname'; | ||
import { RegistryConsumer } from '@bem-react/di'; | ||
import { IButtonProps } from '../Button/Button'; | ||
// No Header or Footer imports | ||
const cnApp = cn('App'); | ||
const cnButton = cn('Button'); | ||
const cnPage = cn('Page'); | ||
const cnHeader = cn('Header'); | ||
const cnFooter = cn('Footer'); | ||
@@ -138,12 +127,27 @@ export const App: React.SFC = () => ( | ||
{registries => { | ||
// reading App registry | ||
// Get registry with components | ||
const registry = registries[cnApp()]; | ||
// Get the needed version of the component based on registry | ||
const Header = registry.get(cnHeader()); | ||
const Footer = registry.get(cnFooter()); | ||
// taking desktop or mobile version | ||
const Button = registry.get<IButtonProps>(cnButton()); | ||
return <Button text="Hello there!" />; | ||
return( | ||
<div className={ cnPage() }> | ||
<Header /> | ||
<Content /> | ||
<Footer /> | ||
</div> | ||
); | ||
}} | ||
</RegistryConsumer> | ||
); | ||
export default App; | ||
``` | ||
So you could use different versions of your app e.g. for conditional rendering on your server side or to create separate bundles | ||
```javascript | ||
import { AppDesktop } from './path-to/App@desktop'; | ||
import { AppMobile } from './path-to/App@mobile'; | ||
``` |
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
13130
102
153
3
2