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

func-di

v1.4.7

Published

A functional, immutable, type safe and simple dependency injection library inspired by angular.

Downloads

20

Readme

func-di

English | 简体中文


Auto Test CI Publish CI npm version

A functional, immutable, type safe and simple dependency injection library inspired by Angular.

Why func-di

  • 0 dependency: extremely lightweight
  • functional: no class and decorator
  • immutable: stable dependency if you do not dynamically switch the whole ioc container
  • type safe: good develop experience and static type checks

Installation

For Node.JS tool chains:

npm install func-di # or other package manager

Or no tool chain:

<!-- Use `importmap` to make alias for `func-di`, `react`, `react-dom` and thier dependencies. -->
<!-- You can choose any other CDN URL you'd like. -->
<script type="importmap">
  {
    "imports": {
      "func-di": "https://unpkg.com/func-di/dist/index.browser.esm.min.js",
      "func-di/react": "https://unpkg.com/func-di/dist/react.browser.esm.min.js",
      "func-di/hooks": "https://unpkg.com/func-di/dist/hooks.browser.esm.min.js",
      "react": "https://ga.jspm.io/npm:[email protected]/index.js",
      "react-dom/client": "https://ga.jspm.io/npm:[email protected]/index.js",
      "process": "https://ga.jspm.io/npm:[email protected]/browser.js",
      "scheduler": "https://ga.jspm.io/npm:[email protected]/index.js"
    }
  }
</script>
<script type="module">
  import { token, inject, container } from "func-di";
  import { useInjection } from "func-di/hooks";
  import { Inject, Provide, connectInjectionHooks } from "func-di/react";
  import React from "react";
  import ReactDOM from "react-dom/client";
  // Support ES module out of the box
</script>

Tired of the importmap code? Try es-modularize!

If want to use React support in browser directly without Node.JS tool chain, add the above importmap code before all script elements in your HTML. Note that importmap is not currently supported in every modern browser (e.g. FireFox, Safari). You might need this tool: https://github.com/guybedford/es-module-shims for those browsers.

Usage

See test cases for more details.

TypeScript

import { token, inject, factory, implementation, container, provide } from "func-di";
// 0. Define dependency types with interface/type:
interface ServiceA {
  foo(): number;
}

interface ServiceB {
  bar: string;
}
// 1. Define dependencies with interface/type:
const dependencyA = token<ServiceA>("ServiceA");
const dependencyB = token<ServiceB>("ServiceB");

// 2. Implement the dependencies:

// Implement a dependency factory without other dependency:
const serviceAImpl = factory(dependencyA, () => {
  return {
    foo() {
      return 111;
    },
  };
});
// Or implement a dependency factory with other injected dependency:
// Inject as parameter of factory function:
const serviceBImpl = inject({
  serviceA: dependencyA,
}).implements(dependencyB, function ({ serviceA }) {
  // You can get the injected dependency via both parameter and `this` context.
  assert.strictEqual(this.serviceA, serviceA);
  return {
    bar: serviceA.foo().toFixed(2),
  };
},
// To define how to dispose the dependency instance and release resources, you can pass an optional function.
(instance) => {
  console.log('dispose instance of ServiceB:', instance.bar);
});
// Inject container itself as parameter of factory function:
const serviceBImpl2 = dependencyB.implementAs(function (ioc) {
  // You can get the IoC container instance via both parameter and `this` context.
  assert.strictEqual(this, ioc);
  // Functions on IoC container instances have nothing to do with `this` context.
  // It's OK to destruct to use them as variables.
  // Actually all functions of `func-di` have nothing to do with `this` context.
  const { request } = ioc;
  return {
    bar: request(dependencyA).foo().toFixed(2),
  };
});
// Or implement a dependency with a direct instance:
const serviceBDirectImpl = implementation(dependencyB, { bar: "777" });

// 3. Create IOC container with service providers:

const iocContainer = container([
  // Use `stateful` if you want to cache the instance.
  // Use `stateless` if you want to create instance on every request.
  provide.stateful(serviceAImpl),
  provide.stateful(serviceBImpl),
]);

// 4. Get implementation from the container:
const serviceB = iocContainer.request(dependencyB);
console.log(serviceB.bar); // 111.00

// 5. You can create child containers to overwrite some dependency implementaions.

const childContainer = iocContainer.fork([provide.stateful(serviceBDirectImpl)]);

console.log(childContainer.request(dependencyB).bar); // 777

// 6. To release stateful instance, call `clear()`. To release all resources, call `dispose()`.
// Note that `dispose()` will also dispose its child containers.
// Clear instance cache. `stateful` providers will create new dependency instance when requested.
iocContainer.clear();     // output: dispose instance of ServiceB: 111.00
// Clear instance cache, registered dependency info and perform `dispose()` on its children.
iocContainer.dispose();   
// After calling `dispose()`, all calls of method on this container instance will result an error.

JavaScript

If you are using JavaScript with TypeScript language service (using vscode, Web Storm or other editor and installed func-di via Node.JS tool chain), you can pass a default instance to token for type inference.

// @ts-check
// ^^^^^^^^^ Use this directive to enable TypeScript checks in JavaScript code.
import { token, inject, factory, implementation, container, provide } from "func-di";
// 1. Define dependencies with default implementation. Types will be inferred automatically.
const dependencyA = token("ServiceA", {
  /**
   * Use type annotation in `JSDoc` comment for concrete types.
   * In this example, TypeScript will infer return type as `0` without this type annotation.
   * @returns {number}
   */
  foo() {
    return 0;
  },
});
const dependencyB = token("ServiceB", { bar: "" });

// The next steps are the same with the TypeScript example.

You can also annotate your dependency token with generic type Token in this way (not recommended, use TypeScript directly instead):

// @ts-check
// ^^^^^^^^^ Use this directive to enable TypeScript checks in JavaScript code.
import { token, inject, factory, implementation, container, provide } from "func-di";
// 0. Define your dependency types with interface/type:
/**
 * @typedef {{
 *  foo(): number;
 * }} ServiceA
 * @typedef {{
 *  bar: string;
 * }} ServiceB
 */
// 1. Define dependencies with `JSDoc` type annotation:
/** @type {import('func-di').Token<ServiceA>} */
const dependencyA = token("ServiceA");
/** @type {import('func-di').Token<ServiceB>} */
const dependencyB = token("ServiceB");

// The next steps are the same with the TypeScript example.

React Support

You can use these APIs to connect func-di with React components. Use Inject to create consumer components and Provide to create provider components.

Using Inject does not create nested HOCs. Your render function and dependency injection will be executed within the same component's render logic.

Using Provide will create a nested component. It has only one IoCContext.Provider element inside and provides the corresponding container as value.

// Relevant dependency declarations and implementations
import React from "react";
import ReactDOM from "react-dom/client";
import { Inject, Provide } from "func-di/react";
interface CountService {
  count: number;
}
interface MessageService {
  renderMessage(tag: string): React.ReactElement;
}
const countService = token<CountService>("count");
const rootCountImpl = implementation(countService, { count: 6 });
const messageService = token<MessageService>("message");
const msgImpl = inject({ countService }).implements(messageService, ({ countService }) => {
  return {
    renderMessage(tag) {
      return (
        <div>
          <span>{tag}</span>
          <span>{countService.count}</span>
        </div>
      );
    },
  };
});

// Create a consumer component
const CountMessage = Inject({ countService, messageService })
  .props<{ tag: string }>()
  .composed.fc(({ messageService, tag }) => messageService.renderMessage(tag));

// Create a provider component
const RootIoC = Provide([provide.stateful(rootCountImpl), provide.stateful(msgImpl)]).dependent();

// Use
ReactDOM.createRoot(document.querySelector("#root")!).render(
  <RootIoC>
    <CountMessage tag="foo" />
  </RootIoC>
);

hooks

These APIs are still experimental and may be modified in the future.

You can also use these hooks directly inside react components to get injected dependencies. Like normal hooks, they must be executed within the execution context of the react component.

  • useContainer(): get the IOC container
  • useContainerRequest(token): get the IOC container and request dependency
  • useInjection(token): get dependency within specific context

useInjection is recommended. Please don't forget to wrap your component with connectInjectionHooks to get the correct execution context.

// Some of the same code as above has been omitted.
import { useInjection } from "func-di/hooks";
import { connectInjectionHooks } from "func-di/react";
const Component: React.FC = () => {
  const { count } = useInjection(countService);
  return (
    <div>
      <p>
        <span>injection</span>
        <span>{count}</span>
      </p>
    </div>
  );
};
const ConnectedComponent = connectInjectionHooks(Component);
ReactDOM.createRoot(document.querySelector("#another-root")).render(
  <RootIoC>
    <ConnectedComponent />
  </RootIoC>
);

License

 __________________
< The MIT license! >
 ------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||