npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tiny-container

v1.0.4

Published

A very small (~500B) IoC Container the is easy to use and makes all your dreams come true

Downloads

25

Readme

Tiny Container

Coverage Status

🚦 A very small (~500B) IoC Container the is easy to use and makes all your dreams come true

Features

  • Small (~500B) so it does not affect your size budget
  • Easy to use because the API surface is tiny
  • Fast because of lazy instantiation (does not instantiate until needed)
  • Type safe because it is written in Typescript
  • Keeps your code nice and decoupled

Installation

npm i tiny-container or yarn add tiny-container

Then, you can import it using import { Container } from 'tiny-container';

Api

The Container has three instance methods available. This means that the Container has to be instantiated, e.g., const container = new Container();.

register

The register method adds the class to the list of container services.

NOTE: When using register, an new instance will be created every time the class is retrieved. If you only need a single instance to ever be created, use singleton.

Parameters

| Name | Type | Required | Description | | ------------ | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | string | true | This is the lookup name of the class being added | | class | class | true | This is the uninstantiated class that is being registered | | dependencies | string[] | false | These are the dependencies that will be injected into the constructor of the class. These must also be registered with the container using register or singleton |

Usage

const container = new Container();

// No dependencies
container.register('apiService', ApiService);

// Has "apiService" as a dependency
container.register('fileService', FileService, ['apiService']);

// This will not work and will throw an error because the "serviceThatDoesNotExist" has
// not been registered with the Container.
container.register('queryService', QueryService, ['serviceThatDoesNotExist']);

singleton

The singleton method adds the class to the list of container services, but makes sure it is only ever instantiated once. Once it has been instantiated, the same instance will be returned each time.

Parameters

| Name | Type | Required | Description | | ------------ | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | string | true | This is the lookup name of the class being added | | class | class | true | This is the uninstantiated class that is being registered | | dependencies | string[] | false | These are the dependencies that will be injected into the constructor of the class. These must also be registered with the container using register or singleton |

Usage

const container = new Container();

// No dependencies
container.singleton('apiService', ApiService);

// Has "apiService" as a dependency
container.singleton('fileService', FileService, ['apiService']);

// This will not work and will throw an error because the "serviceThatDoesNotExist" has
// not been registered with the Container.
container.singleton('queryService', QueryService, ['serviceThatDoesNotExist']);

get

The get method retrieves an instantiated class from the container's services with all of the dependencies injected. It also has a second parameter, which is an object, that allows the passing in of more dependencies. This makes the container very flexible.

Parameters

| Name | Type | Required | Description | | ---------------------- | ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | string | true | This is the lookup name of the class being retrieved. It is the same name used when registering the service. | | additionalDependencies | object{ [string]: any } | false | This is an object of additional dependencies that need to be passed in. The key of the object will be name of the dependency and the property will be the dependency |

Usage

// Somewhere that needs the ApiService
import axios from 'axios';

const apiService = container.get('apiService', { request: axios });

apiService.request.get('https://api.domain.com/users/1');

// In the ApiService...
export default class ApiService {
  // Notice the name "request" here matches the key of the object passed in as the second parameter
  // in the "get" method above.
  constructor({ request }) {
    this.request = request;
  }
}

Example

Tiny Container is very easy to use. You add your dependencies to the container and then retrieve them when you need to use them. All of the dependencies will be injected via the constructor so your code is nice and decoupled. This makes testing super easy.

The first thing you need to do is create the container:

src/bootstrap.js

import { Container } from 'tiny-container';

import ApiService from '../services/api';
import FileService from '../services/file';
import Store from '../stores';

const container = new Container();

// The first param is the name of the service. That is how you will retrieve the service later,
// so it may be a good idea to create a "const" or "enum" with the names
// (I'll show an example later)
container.register('apiService', ApiService);
container.register('fileService', FileService);

// Notice the third parameter and how I am using the same names as I did previously.
// That is important. Now, our "Store" will have access to both the "apiService" and
// the "fileService" via its constructor.
container.singleton('store', Store, ['apiService', 'fileService']);

// We need to export the container so we can retrieve our dependencies later
export default container;

Now we will use our container to instantiate our dependencies.

src/index.js

import React from 'react';
import { render } from 'ReactDOM';

import App from './app';
import container from './bootstrap';

// Notice how the use of the name 'store' is the same as in our bootstrap.js file
const store = container.get('store');

render(<App store={store} />, document.getElementById('root'));

Now, let's see how dependencies are injected into the Store.

src/stores/index.js

export default class Store {
  // The first parameter is an object with the dependencies that were declared in the array, as
  // the third argument of "register" or "singleton"
  constructor({ apiService, fileService }) {
    this.apiService = apiService;
    this.fileService = fileService;
  }
}

Recipes

Defining Service Names

Since the names of the services are important, it is a good idea to use an exported const or an enum with the names:

import { Container } from 'tiny-container';

import ApiService from '../services/api';
import FileService from '../services/file';
import Store from '../stores';

const container = new Container();

// Use this wherever you register or retrieve your service
export const SERVICES = {
  apiService: 'apiService',
  fileService: 'fileService',
  store: 'store',
};

container.register(SERVICES.apiService, ApiService);
container.register(SERVICES.fileService, FileService);
container.singleton(SERVICES.store, Store, [SERVICES.apiService, SERVICES.fileService]);

export default container;

In Typescript:

import { Container } from 'tiny-container';

import ApiService from '../services/api';
import FileService from '../services/file';
import Store from '../stores';

const container = new Container();

// Use this wherever you register or retrieve your service
export enum SERVICES {
  ApiService = 'apiService',
  FileService = 'fileService',
  Store = 'store',
}

container.register(SERVICES.ApiService, ApiService);
container.register(SERVICES.FileService, FileService);
container.singleton(SERVICES.Store, Store, [SERVICES.ApiService, SERVICES.FileService]);

export default container;

Connecting MobX stores

Tiny Container is perfect for MobX and hooking up the stores and their dependencies. This is where the idea of this library started.

src/services/api.js

export default class ApiService {
  get() {
    // Could be a GET fetch call
  }

  post() {
    // Could be a POST fetch call
  }
}

src/bootstrap.js

import { Container } from 'tiny-container';

import ApiService from '../services/api';
import UserStore from './stores/user';

const container = new Container();

container.register('apiService', ApiService);
container.singleton('userStore', UserStore, ['apiService']);

export default container;

src/stores/user.js

export default class UserStore {
  constructor({ rootStore, apiService }) {
    // We need access to our root store so we can talk to other stores
    this.rootStore = rootStore;
    // We have access to the apiService because of our bootstrap file above
    this.apiService = apiService;
  }
}

src/stores/root.js

export default class RootStore {
  constructor() {
    // Pass in the root store as a dependency to the get method so it is injected in the constructor
    // of our UserStore
    this.userStore = container.get('userStore', { rootStore: this });
  }
}

src/index.jsx

import React from 'react';
import { render } from 'ReactDOM';
import { Provider } from 'mobx-react';

import App from './app';
import RootStore from './stores/root';

// We don't add the RootStore to the container because we do not want to create a cyclic dependency
// since we need to use the container in the RootStore to resolve the other stores
const store = new RootStore();

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Issues

Open up an issue if you find one. If you can provide a reproduction, then please do. You can use codesandbox.io for this.

Contributing

Every merge into master will publish a new release to NPM. In order to know which version should be should be published, i.e., patch, minor, major, this repo uses keywords in the git commits.

  • No keyword - patch
  • "feat" or "feature" - minor
  • "BREAKING CHANGES" - major

License (MIT)

Check it out here.