Jimple
该项目是 Pimple 依赖注入容器 的端口,适用于 NodeJS 和使用 ES6 提供的功能的浏览器。
所有代码都使用 Mocha 进行了测试,看起来很稳定。 以下是该项目的文档:
Features
好的项目有好的特性。 因为这是 Jimple 支持的功能列表:
- Define services;
- Define factories;
- Define parameters easily;
- Defining services/parameters/factories from another files - because you should be able to split your configuration easily;
- Simple API;
- Runs on NodeJS and on browser;
- Allows extending services easily;
- Allow to get the raw service creator easily;
- Pure Javascript;
- Stable API;
- No dependencies (in nodejs, in browser we need a shim);
- No module loader integrated - You can use any module loader you want;
- Fully tested on each commit;
- 100% code coverage;
- Fully Documented;
- Less than 300 SLOC;
- ~1KB minified and gzipped - Tested on CI using size-limit;
- I already said that it have a really Simple API? :)
Testing without installing anything
如果您喜欢这些功能,请随时在 NodeJS 环境中免费 测试 Jimple,而无需使用 Runkit。 试一试。 :)
Installation
这个包的安装非常简单:其实只要运行就可以安装:
npm install --save @00f100/jimple
如果使用NodeJS(这个安装的包纯粹基于ES 6)
如果你想在浏览器中使用这个包。 您还可以使用 CDN 提供的版本,例如 JSDelivr。 因此,您可以将下面的代码粘贴到页面上,然后开始真正快速地使用 Jimple:
<script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/jimple@latest/src/Jimple.js"></script>
警告:请注意,上面的代码始终使用最新版本的 Jimple。 在生产中,请将 latest 替换为 Releases 页面中的有效版本号 或使用 Bower 或NPM 为您安装固定版本。 =)
请注意,该库的浏览器版本使用由 Babel 编译的版本。 因为这个,也因为浏览器还没有很好地支持 Map
和 Set
,你需要加载 babel-polyfill
(或其他实现Map
和Set
支持的类似 polyfill)< strong>在 在浏览器上加载这个包之前。
Usage
创建一个 Jimple 容器只是创建一个 Jimple 实例的问题:
var Jimple = require("jimple");
var container = new Jimple();
在浏览器中,您可以使用多种方式加载 Jimple:
define(["jimple"], function(Jimple) {
// Code using Jimple here..
});
var Jimple = require("jimple");
<script language="javascript" src="path/to/Jimple.js"></script>
同样,重要的是要注意 Jimple 需要一个 polyfill 来映射和设置类——这并不是所有的支持实际上最新的浏览器 - 为此,您可以在一些选项之间进行选择,例如 babel-polyfill
例如。
Jimple,作为 Pimple 和许多其他依赖注入容器,管理两种不同类型的数据:服务和参数。
Defining services
正如 Pimple 所描述的,服务是作为更大系统的一部分执行某些操作的对象。 服务示例:数据库连接、模板引擎或邮件程序。 几乎任何全局对象都可以是服务。
Jimple 中的服务(在 Pimple 中也是如此!)由返回对象实例的匿名函数定义。 请注意,在 Jimple 中,您不能将生成器用作服务,因为它们将被检测为参数,因此,只有 pure 函数才能成为服务。 然而,与 Pimple 不同的是,这里我们需要调用 Jimple 容器上的 set()
方法,因为 NodeJS 中的 Proxies 似乎不稳定:
// define some services
container.set('session_storage', function (c) {
return new SessionStorage('SESSION_ID');
});
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
注意定义服务的匿名函数可以访问当前容器实例,允许引用其他服务或参数。
对象是按需创建的,就在您获得它们时。 定义的顺序无关紧要。
使用定义的服务也非常简单:
// get the session object
var session = container.get('session');
// the above call is roughly equivalent to the following code:
// var storage = new SessionStorage('SESSION_ID');
// var session = new Session(storage);
Defining factory services
默认情况下,当您获得服务时,Jimple 会自动缓存它的值,始终返回它的相同实例。 如果您希望为所有调用返回不同的实例,请使用 factory()
方法包装您的匿名函数:
container.set('session', container.factory(function (c) {
return new Session(c.get('session_storage'));
}));
现在,每次调用 container.get('session'),将为您返回一个新的 Session
实例。
Defining parameters
定义参数允许从外部简化容器的配置并存储全局值。 在 Jimple 中,参数被定义为任何不是函数的东西:
// define a parameter called cookie_name
container.set('cookie_name', 'SESSION_ID');
如果你像下面这样改变 session_storage
服务定义:
container.set('session_storage', function (c) {
return new SessionStorage(c.get('cookie_name'));
});
你现在可以通过覆盖 cookie_name
轻松地改变 cookie 名称参数而不是重新定义服务定义。
Defining parameters based on environment variables (NodeJS only)
你想根据环境变量在容器中定义参数吗? 没关系! 您可以像这样轻松地定义它:
//define parameter based on environment variable
container.set('cookie_name', process.env.COOKIE_NAME);
并非所有服务总是需要所有服务或参数,您可以使用参数或服务的默认值。 在这种情况下,你可以这样做:
container.set('session_storage', function (c) {
return new SessionStorage(c.has('cookie_name') ? c.get('cookie_name') : 'COOKIE_ID');
});
在这个例子中,如果参数 cookie_name
不存在,SessionStorage 将使用默认的 'COOKIE_ID'
实例化,并且这也适用于服务。 :)
Protecting parameters
因为 Jimple 将任何函数视为服务,您需要使用 protect()
方法包装匿名函数以将它们存储为参数:
container.set('random_func', container.protect(function () {
return Math.random();
}));
Modifying Services after Definition
在某些情况下,您可能想要修改一个 服务定义(注意不能扩展参数,包括受保护的参数)在定义之后。 您可以使用 extend()
方法来定义在服务创建后立即运行的附加代码:
container.set('session_storage', function (c) {
return new SessionStorage(c.get('cookie_name'));
});
container.extend('session_storage', function (storage, c) {
storage.someMethod();
return storage;
});
第一个参数是要扩展的服务的名称,第二个参数是获取访问权限的函数到对象实例和容器。
Extending a Container
如果您一遍又一遍地使用相同的库,您可能希望将某个项目的某些服务重用到下一个项目; 通过 duck-typing 实现以下对象结构,将您的服务打包到提供者中:
var provider = {
"register": function(c) {
// Define your services and parameters here
}
}
因为 JS 还不支持接口,所以我们无法验证提供者的结构。
创建具有该结构的对象后,您可以将其注册到容器中:
container.register(provider);
Extending a container from a file (NodeJS/Browserify only)
如果您想拆分容器的配置(因此每个文件更..简单和具体),您可以像这样创建多个文件:
file1。 js:
module.exports.register = function(container) {
// Define your services and parameters here
}
要加载包,您可以执行以下操作:
container.register(require("./file1"))
您可以通过执行以下操作来创建包含容器配置的目录:
xpto/file1.js:
module.exports.register = function(container) {
// Define your services and parameters here
}
xpto/file2.js:
module.exports.register = function(container) {
// Define your services and parameters here
}
xpto/index.js:
module.exports.register = function(container) {
container.register(require("./file1"));
container.register(require("./file2"));
}
最后,在一些创建容器的文件中:
container.register(require("./xpto"));
注意 index.js 文件首先加载到 xpto 目录,然后 index.js 文件加载文件 file1.js 和 file2 .js 存在于该目录中。 您可以对任意数量的目录执行此操作。 :)
Extending a container using the shorthand function
您可以使用导出的 provider
速记方法通过简单的回调轻松创建容器的配置:
const { provider } = require('jimple');
module.exports = provider((container) => {
// Define your services and parameters here
});
它的使用方式与之前的方法完全相同(来自文件),但它也可以用于同时导出多个配置:
const { provider } = require('jimple');
module.exports = {
configurationA: provider((container) => { ... }),
configurationB: provider((container) => { ... }),
};
Fetching the Service Creation Function
当您访问一个对象时,Jimple 会自动调用您定义的匿名函数,它会为您创建服务对象。 如果你想获得对该函数的原始访问权限,但又不想保护()
该服务,你可以使用raw()
方法直接访问该函数:
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
var sessionFunction = container.raw('session');
Proxy - ES6
在 ES6 中,我们可以使用 Proxy 对象来为一些基本操作定义自定义行为 (点击此处查看更多信息)。 这使得我们可以自定义Jimple以获得与Pimple提供的非常接近的体验,它可以直接获取服务和参数而不调用get()
或者设置服务和参数而不调用set() 。
但是,要访问该模式,您不能使用 Jimple 构造函数,而是使用称为 proxy()
的静态方法。 所以,下面的代码:
const container = Jimple.proxy();
container['session_storage'] = function (c) {
return new SessionStorage('SESSION_ID');
};
container['session'] = function (c) {
return new Session(c['session_storage']);
};
实际上等同于:
const container = new Jimple();
container.set('session_storage', function (c) {
return new SessionStorage('SESSION_ID');
});
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
请注意 proxy()
方法可以接收一个参数,就像 Jimple
构造函数一样。 所以你可以注意到:
const container = Jimple.proxy({"SESSION_ID": "test"});
实际上等同于:
const container = new Jimple({"SESSION_ID": "test"});
顺便说一下,注意 Proxy 是一个并非真正在所有地方都受支持的 API(例如,它在 NodeJS >= 6 中受支持)。 例如,我们不建议在浏览器环境中使用它。
当然,此选项有一些限制:基本上,您不能使用某些名称,例如容器中可用方法的名称作为您的服务/参数的名称。 所以像这样:
const container = Jimple.proxy();
container.set = 42;
被禁止并自动抛出异常。
Last, but not least important: Customization
你想定制 Jimple 的功能吗? 你可以! 只需使用 ES6 的类语法扩展它:
var Jimple = require("jimple");
class ABigContainer extends Jimple {
// Overwrite any of Jimple's method here, or add new methods
}
var container = new ABigContainer();
良好的自定义。 :)
Jimple
This project is a port of Pimple Dependency Injection container for NodeJS and for browsers using features provided by ES6.
All the code is tested using Mocha and seems to be stable. Below is the documentation for the project:
Features
Good projects have good features. And because this here's the list of features that Jimple supports:
- Define services;
- Define factories;
- Define parameters easily;
- Defining services/parameters/factories from another files - because you should be able to split your configuration easily;
- Simple API;
- Runs on NodeJS and on browser;
- Allows extending services easily;
- Allow to get the raw service creator easily;
- Pure Javascript;
- Stable API;
- No dependencies (in nodejs, in browser we need a shim);
- No module loader integrated - You can use any module loader you want;
- Fully tested on each commit;
- 100% code coverage;
- Fully Documented;
- Less than 300 SLOC;
- ~1KB minified and gzipped - Tested on CI using size-limit;
- I already said that it have a really Simple API? :)
Testing without installing anything
If you liked that features, feel free to test Jimple free on a NodeJS environment without installing anything on your machine by using Runkit. Give it a try. :)
Installation
The installation of this package is very simple: In fact, it can be installed by just running:
npm install --save @00f100/jimple
If using NodeJS (this installs the package based purely on ES 6)
If you want to use this package in the browser. You can also use the version provided by a CDN, like JSDelivr. So you can paste the code below on a page and start using Jimple really fast:
<script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/npm/jimple@latest/src/Jimple.js"></script>
WARNING: Please note that the code above uses always the latest version of Jimple. In production, please replace latest with a valid version number from the Releases page or use Bower or NPM to install a fixed version for you. =)
Note that the browser version of this library uses a version compiled by Babel. And because this, and because browsers does not have great support to Map
and Set
yet, you will need to load babel-polyfill
(or some other similar polyfill that implements Map
and Set
support) before loading this package on the browser.
Usage
Creating a Jimple Container is just a matter of creating a Jimple instance:
var Jimple = require("jimple");
var container = new Jimple();
In the browser, you can load Jimple using various ways:
define(["jimple"], function(Jimple) {
// Code using Jimple here..
});
var Jimple = require("jimple");
<script language="javascript" src="path/to/Jimple.js"></script>
Again, it's important to note that Jimple needs of a polyfill to Map and Set classes - which are not supported by all the latest browsers actually - to do so, you can choice between some options, like babel-polyfill
for example.
Jimple, as Pimple and many other dependency injections containers, manage two different kind of data: services and parameters.
Defining services
As Pimple describes, a service is an object that does something as part of a larger system. Examples of services: a database connection, a templating engine, or a mailer. Almost any global object can be a service.
Services in Jimple (and in Pimple too!) are defined by anonymous functions that return an instance of an object. Note that, in Jimple, you cannot use a generator as a service, as they will be detected as parameters, so, just pure functions can be a service. Different from Pimple, however, here we need to call the method set()
on Jimple container, as Proxies in NodeJS seems to not be stable:
// define some services
container.set('session_storage', function (c) {
return new SessionStorage('SESSION_ID');
});
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
Notice that the anonymous function that define a service has access to the current container instance, allowing references to other services or parameters.
The objects are created on demand, just when you get them. The order of the definitions does not matter.
Using the defined services is very easy, too:
// get the session object
var session = container.get('session');
// the above call is roughly equivalent to the following code:
// var storage = new SessionStorage('SESSION_ID');
// var session = new Session(storage);
Defining factory services
By default, when you get a service, Jimple automatically cache it's value, returning always the same instance of it. If you want a different instance to be returned for all calls, wrap your anonymous function with the factory()
method:
container.set('session', container.factory(function (c) {
return new Session(c.get('session_storage'));
}));
Now, each time you call container.get('session')
, a new instance of Session
is returned for you.
Defining parameters
Defining a parameter allows to ease the configuration of your container from the outside and to store global values. In Jimple, parameters are defined as anything that it's not a function:
// define a parameter called cookie_name
container.set('cookie_name', 'SESSION_ID');
If you change the session_storage
service definition like below:
container.set('session_storage', function (c) {
return new SessionStorage(c.get('cookie_name'));
});
You can now easily change the cookie name by overriding the cookie_name
parameter instead of redefining the service definition.
Defining parameters based on environment variables (NodeJS only)
Do you wanna do define parameters in the container based on environment variables? It's okay! You can define it easily like that:
//define parameter based on environment variable
container.set('cookie_name', process.env.COOKIE_NAME);
Not all services need all the services or parameters always, and you may do well with a default value for an parameter or service. In this case, you can do something like that:
container.set('session_storage', function (c) {
return new SessionStorage(c.has('cookie_name') ? c.get('cookie_name') : 'COOKIE_ID');
});
In this example, if the parameter cookie_name
does not exist, the SessionStorage will be instantiated with the default 'COOKIE_ID'
, and this works for services too. :)
Protecting parameters
Because Jimple see anything that is a function as a service, you need to wrap anonymous functions with the protect()
method to store them as parameters:
container.set('random_func', container.protect(function () {
return Math.random();
}));
Modifying Services after Definition
In some cases you may want to modify a service definition (note that you cannot extend a parameter, including protected parameters) after it has been defined. You can use the extend()
method to define additional code to be run on your service just after it is created:
container.set('session_storage', function (c) {
return new SessionStorage(c.get('cookie_name'));
});
container.extend('session_storage', function (storage, c) {
storage.someMethod();
return storage;
});
The first argument is the name of the service to extend, the second a function that gets access to the object instance and the container.
Extending a Container
If you use the same libraries over and over, you might want to reuse some services from one project to the next one; package your services into a provider by implementing the following object structure by duck-typing:
var provider = {
"register": function(c) {
// Define your services and parameters here
}
}
Because JS has no support to interfaces yet, we cannot validate too much the structure of the provider.
After creating a object with that structure, you can register it in the container:
container.register(provider);
Extending a container from a file (NodeJS/Browserify only)
If you want to split your container's configuration (so each file is more..simple and specific), you can create multiple files like that:
file1.js:
module.exports.register = function(container) {
// Define your services and parameters here
}
To load the package, you can do something like:
container.register(require("./file1"))
You can, inclusive, create directories with container's configuration, by doing something like that:
xpto/file1.js:
module.exports.register = function(container) {
// Define your services and parameters here
}
xpto/file2.js:
module.exports.register = function(container) {
// Define your services and parameters here
}
xpto/index.js:
module.exports.register = function(container) {
container.register(require("./file1"));
container.register(require("./file2"));
}
And, finally, in some file creating the container:
container.register(require("./xpto"));
Note that the index.js file is loaded first on xpto directory, and that index.js file loads the files file1.js and file2.js present on that directory. You can do that for any number of directories. :)
Extending a container using the shorthand function
You can use the exported provider
shorthand method to easily create your container's configuration with a simple callback:
const { provider } = require('jimple');
module.exports = provider((container) => {
// Define your services and parameters here
});
It can be used exact same way as the previous method (from a file), but it also can be used to export multiple configurations at the same time:
const { provider } = require('jimple');
module.exports = {
configurationA: provider((container) => { ... }),
configurationB: provider((container) => { ... }),
};
Fetching the Service Creation Function
When you access an object, Jimple automatically calls the anonymous function that you defined, which creates the service object for you. If you want to get raw access to this function, but don't want to protect()
that service, you can use the raw()
method to access the function directly:
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
var sessionFunction = container.raw('session');
Proxy - ES6
In ES6, we can use a Proxy object to define custom behavior for some fundamental operations (see more here clicking here). That allows us to customize Jimple to have a experience very near from that provided by Pimple, which can get services and parameters directly without calling get()
or set services and parameters without calling set()
.
However, for access that mode you cannot use the Jimple constructor, but a static method called proxy()
. So, the code below:
const container = Jimple.proxy();
container['session_storage'] = function (c) {
return new SessionStorage('SESSION_ID');
};
container['session'] = function (c) {
return new Session(c['session_storage']);
};
Is in fact equivalent to that:
const container = new Jimple();
container.set('session_storage', function (c) {
return new SessionStorage('SESSION_ID');
});
container.set('session', function (c) {
return new Session(c.get('session_storage'));
});
Please note that the proxy()
method can receive a parameter, like the Jimple
constructor. So you can note that:
const container = Jimple.proxy({"SESSION_ID": "test"});
Is in fact equivalent for:
const container = new Jimple({"SESSION_ID": "test"});
By the way, observe that Proxy is an API that's not really supported everywhere (it's supported in NodeJS >= 6, for example). So we do not recommend it's use in browser environments, for example.
Of course, this option has some limitations: basically, you CANNOT use certain names like the names of the methods available in the container as names of your services/parameters. So something like:
const container = Jimple.proxy();
container.set = 42;
Is forbidden and throws an exception automatically.
Last, but not least important: Customization
Do you wanna to customize Jimple's functionally? You can! Just extend it using ES6's class syntax:
var Jimple = require("jimple");
class ABigContainer extends Jimple {
// Overwrite any of Jimple's method here, or add new methods
}
var container = new ABigContainer();
Good customization. :)