├── .github └── workflows │ ├── main.yml │ └── size.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── LICENSE ├── README.md ├── example ├── .npmignore ├── index.html ├── index.tsx ├── package.json └── tsconfig.json ├── jest.config.js ├── package.json ├── setupTests.js ├── src └── index.tsx ├── test ├── di.test.tsx └── registry.test.ts ├── tsconfig.json └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node: ['16.x'] 11 | 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v2 15 | 16 | - name: Use Node ${{ matrix.node }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node }} 20 | 21 | - name: Install deps and build (with cache) 22 | uses: bahmutov/npm-install@v1 23 | 24 | - name: Lint 25 | run: yarn lint 26 | 27 | - name: Test 28 | run: yarn test --ci --coverage --maxWorkers=2 29 | 30 | - name: Build 31 | run: yarn build 32 | -------------------------------------------------------------------------------- /.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | *.code-workspace 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.2.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Roman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EZ-DI 2 | 3 | ## ALPHA VERSION. NOT FOR PRODUCTION USAGE 4 | 5 | Proof of concept tiny dependency injection React library. 6 | 7 | - Small (< 0.5 Kb) 8 | - Easy to use 9 | 10 | ```typescript 11 | // some components in project 12 | const ButtonBase = () => 13 | const RedButton = () => 14 | 15 | // import diBlock and DiProvider 16 | import { DiProvider, diBlock } from '@vanilla-wave/ez-di'; 17 | 18 | // 1. in target component. Make component swapable 19 | const Button = diBlock('Button')(ButtonBase) 20 | 21 | // 2. Wrap in provider and set desired component in registry 22 | const App = () => ( 23 | 24 | 30 | ``` 31 | 32 | ## Install 33 | 34 | ```bash 35 | npm i @vanilla-wave/ez-di 36 | ``` 37 | 38 | ## How it works 39 | 40 | Main idea - give opportunity to swap components in app tree without writting code. 41 | 42 | Wrapping component into `diBlock('ComponentName', Component)` creates slot. In place of component ` 92 | ); 93 | const Button = diBlock('Button')(ButtonBase); 94 | ``` 95 | 96 | 0. Wrap your app in DiRegistry. Optional – you can use registry type here ` ... >` 97 | 98 | ```typescript 99 | interface RegistryType { 100 | Button: React.ComponentType; 101 | } 102 | 103 | const OverrideButton: FC<{ label: number }> = ({ label }) => ( 104 | 105 | ); 106 | 107 | const App = () => ( 108 |
109 | {/* @ts-expect-error */} 110 | registry={{ Button: OverrideButton }}> 111 |
114 | ); 115 | ``` 116 | 117 | 2. Create components for slots. When you set component in registry it will render instead of default component(wrapped in diBlock) 118 | 119 | **Important** – override component should have same props as base components. 120 | 121 | ```typescript 122 | const RedButton: FC = ({ label }) => ( 123 | 124 | ); 125 | ``` 126 | 127 | 3. Now you can manage slots content with registry value. For example: 128 | 129 | - set another components for mobile device 130 | - set another component if user in experiment 131 | - etc 132 | 133 | Falsy value will be ignored, and default component will be rendered. 134 | 135 | ```typescript 136 | const registry = { 137 | Button: isUserInExperiment ? RedButton : undefined, 138 | } 139 | 140 | const App = () => ( 141 | 142 | ; 7 | const ButtonBase2 = () => ; 8 | 9 | const RedButton = () => ; 10 | const BlueButton = () => ; 11 | const Button = diBlock('Button')(ButtonBase); 12 | const Button2 = diBlock('Button2')(ButtonBase2); 13 | 14 | describe('basic usage', () => { 15 | it('component found in registry -> render from registry', () => { 16 | const App = () => ( 17 | 18 | 80 | ); 81 | const Button = diBlock('Button')(ButtonBase); 82 | const OverrideButton: FC = ({ label }) => ( 83 | 84 | ); 85 | 86 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 87 | const App = () => ( 88 | 89 | 99 | ); 100 | const Button = diBlock('Button')(ButtonBase); 101 | const OverrideButton: FC = ({ label }) => ( 102 | 103 | ); 104 | 105 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 106 | const App = () => ( 107 | 108 | {/* @ts-expect-error */} 109 | 123 | ); 124 | const Button = diBlock('Button')(ButtonBase); 125 | const OverrideButton: FC = ({ label }) => ( 126 | 127 | ); 128 | 129 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 130 | const App = () => ( 131 | 132 | 142 | ); 143 | const Button = diBlock('Button')(ButtonBase); 144 | const OverrideButton: FC<{ label: number }> = ({ label }) => ( 145 | 146 | ); 147 | 148 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 149 | const App = () => ( 150 |
151 | {/* @ts-expect-error */} 152 | registry={{ Button: OverrideButton }}> 153 |
156 | ); 157 | 158 | // no type errors 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/registry.test.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from '../src'; 2 | 3 | const Foo = () => {}; 4 | const Bar = (x: string) => x; 5 | 6 | /* eslint-disable @typescript-eslint/no-unused-vars */ 7 | describe('Registry', () => { 8 | describe('basic usage', () => { 9 | it('create without initial value', () => { 10 | const r = new Registry(); 11 | }); 12 | 13 | it('create with initial value', () => { 14 | const r = new Registry({ Foo }); 15 | }); 16 | 17 | it('get saved instance', () => { 18 | const r = new Registry({ Foo }); 19 | expect(r.get('Foo')).toBe(Foo); 20 | }); 21 | 22 | it('save instance type', () => { 23 | const r = new Registry({ Foo, Bar }); 24 | const x: typeof Foo = r.get('Foo'); 25 | }); 26 | }); 27 | 28 | describe('extend registry', () => { 29 | it('can extend registry', () => { 30 | const r = new Registry({ Foo }); 31 | r.extend({ Bar }); 32 | }); 33 | 34 | it('extend then get instance', () => { 35 | const r = new Registry({ Foo }); 36 | 37 | const y = r.extend({ Bar }).get('Bar'); 38 | expect(y).toBe(Bar); 39 | }); 40 | 41 | it('save type when extends', () => { 42 | let r1 = new Registry({ Foo }).extend({ Bar }); 43 | 44 | const y: typeof Bar = r1.get('Bar'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // use Node's module resolution algorithm, instead of the legacy TS one 20 | "moduleResolution": "node", 21 | // transpile JSX to React.createElement 22 | "jsx": "react", 23 | // interop between ESM and CJS modules. Recommended by TS 24 | "esModuleInterop": true, 25 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 26 | "skipLibCheck": true, 27 | // error out if import and file system have a casing mismatch. Recommended by TS 28 | "forceConsistentCasingInFileNames": true, 29 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 30 | "noEmit": true, 31 | } 32 | } 33 | --------------------------------------------------------------------------------