├── .codeclimate.yml
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .nycrc
├── .publishrc
├── .travis.yml
├── LICENSE
├── README.md
├── changelog.md
├── package-lock.json
├── package.json
├── spec
├── injector.spec.js
├── install.spec.js
├── register.spec.js
└── vue.js
└── src
├── base.js
├── index.js
├── install.js
└── plugin.js
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | csslint:
3 | enabled: true
4 | duplication:
5 | enabled: true
6 | config:
7 | languages:
8 | javascript:
9 | mass_threshold : 70
10 | checks:
11 | Similar code:
12 | enabled : false
13 | eslint:
14 | enabled: true
15 | checks :
16 | global-require:
17 | enabled: false
18 | no-eq-null:
19 | enabled : false
20 | complexity:
21 | enabled : false
22 | fixme:
23 | enabled: true
24 | ratings:
25 | paths:
26 | - "src/**/*.js"
27 | exclude_paths:
28 | - spec/**/
29 | ecmaFeatures:
30 | modules: true
31 | jsx: true
32 | env:
33 | amd: true
34 | browser: true
35 | es6: true
36 | jquery: true
37 | node: true
38 |
39 | # http://eslint.org/docs/rules/
40 | rules:
41 | # Possible Errors
42 | comma-dangle: [2, never]
43 | no-cond-assign: 2
44 | no-console: 0
45 | no-constant-condition: 2
46 | no-control-regex: 2
47 | no-debugger: 2
48 | no-dupe-args: 2
49 | no-dupe-keys: 2
50 | no-duplicate-case: 2
51 | no-empty: 2
52 | no-empty-character-class: 2
53 | no-ex-assign: 2
54 | no-extra-boolean-cast: 2
55 | no-extra-parens: 0
56 | no-extra-semi: 2
57 | no-func-assign: 2
58 | no-inner-declarations: [2, functions]
59 | no-invalid-regexp: 2
60 | no-irregular-whitespace: 2
61 | no-negated-in-lhs: 2
62 | no-obj-calls: 2
63 | no-regex-spaces: 2
64 | no-sparse-arrays: 2
65 | no-unexpected-multiline: 2
66 | no-unreachable: 2
67 | use-isnan: 2
68 | valid-jsdoc: 0
69 | valid-typeof: 2
70 | eqeqeq: [2, allow-null]
71 |
72 | # Best Practices
73 | accessor-pairs: 2
74 | block-scoped-var: 0
75 | complexity: [2, 6]
76 | consistent-return: 0
77 | curly: 0
78 | default-case: 0
79 | dot-location: 0
80 | dot-notation: 0
81 | eqeqeq: 2
82 | guard-for-in: 2
83 | no-alert: 2
84 | no-caller: 2
85 | no-case-declarations: 2
86 | no-div-regex: 2
87 | no-else-return: 0
88 | no-empty-label: 2
89 | no-empty-pattern: 2
90 | no-eq-null: 2
91 | no-eval: 2
92 | no-extend-native: 2
93 | no-extra-bind: 2
94 | no-fallthrough: 2
95 | no-floating-decimal: 0
96 | no-implicit-coercion: 0
97 | no-implied-eval: 2
98 | no-invalid-this: 0
99 | no-iterator: 2
100 | no-labels: 0
101 | no-lone-blocks: 2
102 | no-loop-func: 2
103 | no-magic-number: 0
104 | no-multi-spaces: 0
105 | no-multi-str: 0
106 | no-native-reassign: 2
107 | no-new-func: 2
108 | no-new-wrappers: 2
109 | no-new: 2
110 | no-octal-escape: 2
111 | no-octal: 2
112 | no-proto: 2
113 | no-redeclare: 2
114 | no-return-assign: 2
115 | no-script-url: 2
116 | no-self-compare: 2
117 | no-sequences: 0
118 | no-throw-literal: 0
119 | no-unused-expressions: 2
120 | no-useless-call: 2
121 | no-useless-concat: 2
122 | no-void: 2
123 | no-warning-comments: 0
124 | no-with: 2
125 | radix: 2
126 | vars-on-top: 0
127 | wrap-iife: 2
128 | yoda: 0
129 |
130 | # Strict
131 | strict: 0
132 |
133 | # Variables
134 | init-declarations: 0
135 | no-catch-shadow: 2
136 | no-delete-var: 2
137 | no-label-var: 2
138 | no-shadow-restricted-names: 2
139 | no-shadow: 0
140 | no-undef-init: 2
141 | no-undef: 0
142 | no-undefined: 0
143 | no-unused-vars: 0
144 | no-use-before-define: 0
145 |
146 | # Node.js and CommonJS
147 | callback-return: 2
148 | global-require: 2
149 | handle-callback-err: 2
150 | no-mixed-requires: 0
151 | no-new-require: 0
152 | no-path-concat: 2
153 | no-process-exit: 2
154 | no-restricted-modules: 0
155 | no-sync: 0
156 |
157 | # Stylistic Issues
158 | array-bracket-spacing: 0
159 | block-spacing: 0
160 | brace-style: 0
161 | camelcase: 0
162 | comma-spacing: 0
163 | comma-style: 0
164 | computed-property-spacing: 0
165 | consistent-this: 0
166 | eol-last: 0
167 | func-names: 0
168 | func-style: 0
169 | id-length: 0
170 | id-match: 0
171 | indent: 0
172 | jsx-quotes: 0
173 | key-spacing: 0
174 | linebreak-style: 0
175 | lines-around-comment: 0
176 | max-depth: 0
177 | max-len: 0
178 | max-nested-callbacks: 0
179 | max-params: 0
180 | max-statements: [2, 30]
181 | new-cap: 0
182 | new-parens: 0
183 | newline-after-var: 0
184 | no-array-constructor: 0
185 | no-bitwise: 0
186 | no-continue: 0
187 | no-inline-comments: 0
188 | no-lonely-if: 0
189 | no-mixed-spaces-and-tabs: 0
190 | no-multiple-empty-lines: 0
191 | no-negated-condition: 0
192 | no-nested-ternary: 0
193 | no-new-object: 0
194 | no-plusplus: 0
195 | no-restricted-syntax: 0
196 | no-spaced-func: 0
197 | no-ternary: 0
198 | no-trailing-spaces: 0
199 | no-underscore-dangle: 0
200 | no-unneeded-ternary: 0
201 | object-curly-spacing: 0
202 | one-var: 0
203 | operator-assignment: 0
204 | operator-linebreak: 0
205 | padded-blocks: 0
206 | quote-props: 0
207 | quotes: 0
208 | require-jsdoc: 0
209 | semi-spacing: 0
210 | semi: 0
211 | sort-vars: 0
212 | space-after-keywords: 0
213 | space-before-blocks: 0
214 | space-before-function-paren: 0
215 | space-before-keywords: 0
216 | space-in-parens: 0
217 | space-infix-ops: 0
218 | space-return-throw-case: 0
219 | space-unary-ops: 0
220 | spaced-comment: 0
221 | wrap-regex: 0
222 |
223 | # ECMAScript 6
224 | arrow-body-style: 0
225 | arrow-parens: 0
226 | arrow-spacing: 0
227 | constructor-super: 0
228 | generator-star-spacing: 0
229 | no-arrow-condition: 0
230 | no-class-assign: 0
231 | no-const-assign: 0
232 | no-dupe-class-members: 0
233 | no-this-before-super: 0
234 | no-var: 0
235 | object-shorthand: 0
236 | prefer-arrow-callback: 0
237 | prefer-const: 0
238 | prefer-reflect: 0
239 | prefer-spread: 0
240 | prefer-template: 0
241 | require-yield: 0
242 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true,
4 | "commonjs": true,
5 | "es6": true
6 | },
7 | "globals" : {
8 | "process" : false,
9 | "API_URL" : false
10 | },
11 | "extends": "eslint:recommended",
12 | "plugins" : [],
13 | "parserOptions": {
14 | "sourceType": "module"
15 | },
16 | "rules": {
17 | "indent": [
18 | "warn",
19 | 2
20 | ],
21 | "linebreak-style": [
22 | "off",
23 | "unix"
24 | ],
25 | "quotes": [
26 | "error",
27 | "single"
28 | ],
29 | "semi": [
30 | "warn",
31 | "always"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lcov.info
2 | dist
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # node-waf configuration
27 | .lock-wscript
28 |
29 | # Compiled binary addons (http://nodejs.org/api/addons.html)
30 | build/Release
31 |
32 | # Dependency directories
33 | node_modules
34 | jspm_packages
35 |
36 | # Optional npm cache directory
37 | .npm
38 |
39 | # Optional REPL history
40 | .node_repl_history
41 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # dev files
2 | .codeclimate.yml
3 | .travis.yml
4 | .eslintrc.json
5 | .nycrc
6 | .publishrc
7 | spec
8 | lcov.info
9 |
10 |
11 | # Logs
12 | logs
13 | *.log
14 | npm-debug.log*
15 |
16 | # Runtime data
17 | pids
18 | *.pid
19 | *.seed
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 |
27 | # nyc test coverage
28 | .nyc_output
29 |
30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
31 | .grunt
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (http://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules
41 | jspm_packages
42 |
43 | # Optional npm cache directory
44 | .npm
45 |
46 | # Optional REPL history
47 | .node_repl_history
48 |
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "lines" : 80,
3 | "statements" : 80,
4 | "branches" : 80,
5 | "include" : ["src/**/*.js"],
6 | "extension" : [".js", ".vue"],
7 | "reporter" : ["html", "lcov", "text-summary"],
8 | "cache" : false,
9 | "all" : true,
10 | "check-coverage" : true
11 | }
12 |
--------------------------------------------------------------------------------
/.publishrc:
--------------------------------------------------------------------------------
1 | {
2 | "validations": {
3 | "vulnerableDependencies": true,
4 | "uncommittedChanges": true,
5 | "untrackedFiles": true,
6 | "sensitiveData": true,
7 | "branch": false,
8 | "gitTag": true
9 | },
10 | "confirm": false,
11 | "publishTag": "latest",
12 | "prePublishScript": false,
13 | "postPublishScript": false
14 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | branches :
2 | only :
3 | - master
4 | language : node_js
5 | node_js :
6 | - 6
7 | install :
8 | - npm install
9 |
10 | - npm install codeclimate-test-reporter -g
11 | script :
12 | - npm run lint
13 | - npm test
14 | - npm run coverage
15 | - npm run build
16 |
17 | - npm run lcov-fix
18 | after_script :
19 | - codeclimate-test-reporter < lcov.info
20 | - npm run travis-prepublish
21 | - npm run publish-please
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jack
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-inject
2 | Dependency Injection for vue
3 |
4 | [](https://badge.fury.io/js/vue-inject)
5 | [](https://travis-ci.org/jpex-js/vue-inject)
6 | [](https://codeclimate.com/github/jpex-js/vue-inject)
7 | [](https://codeclimate.com/github/jpex-js/vue-inject/coverage)
8 |
9 | ## Usage
10 | ### Install...
11 | ```
12 | npm install vue-inject --save-dev
13 | ```
14 |
15 | ### Tell Vue about vue-inject...
16 | ```javascript
17 | // main.js
18 | import injector from 'vue-inject';
19 | import Vue from 'vue';
20 | Vue.use(injector);
21 | ```
22 |
23 |
24 | ### Register your services...
25 | ```javascript
26 | // myService.js
27 | import injector from 'vue-inject';
28 | class MyService{
29 | // ...
30 | }
31 | injector.service('myService', MyService);
32 | ```
33 |
34 | ### Declare your dependencies...
35 | ```html
36 | // myComponent.vue
37 |
49 | ```
50 |
51 | ## Application Structure
52 | Example:
53 | ```
54 | /
55 | main.js
56 | app.vue
57 | app_start.js
58 | constants.js
59 | services.js
60 | ```
61 |
62 | ```javascript
63 | // main.js
64 | import Vue from 'vue';
65 | import injector from 'vue-inject';
66 | import App from './app';
67 |
68 | // app_start will load anything that can be injected into your application
69 | require('./app_start');
70 |
71 | // register the injector with Vue
72 | Vue.use(injector);
73 |
74 | // render the main component
75 | new Vue({
76 | render : h => h(App)
77 | });
78 |
79 | ```
80 |
81 | ```javascript
82 | // app_start.js
83 | require('./constants');
84 | require('./services');
85 |
86 | // By requiring these files, the factories and services will be registered with the injector.
87 | // You could just export the factory functions and register them all here, but this would mean
88 | // separating the function from the array of dependencies it uses.
89 | ```
90 |
91 | ```javascript
92 | // constants.js
93 | import injector from 'vue-inject';
94 | import axios from 'axios';
95 |
96 | injector.constant('apiRoot', 'http://www.fake.com/api');
97 | injector.constant('axios', axios);
98 | ```
99 |
100 | ```javascript
101 | // services.js
102 | import injector from 'vue-inject';
103 |
104 | function apiUrlBuilder(apiRoot){
105 | return function(path){
106 | return apiRoot + '/' + path;
107 | }
108 | }
109 | injector.factory('apiUrlBuilder', 'apiRoot', apiUrlBuilder);
110 |
111 | function api(apiUrlBuilder, axios){
112 | this.get(path){
113 | var url = apiUrlBuilder(path);
114 | return axios.get(url);
115 | };
116 | }
117 | injector.service('api', ['apiUrlBuilder', 'axios'], api);
118 | ```
119 |
120 | ```html
121 | // app.vue
122 |
123 | ...
124 |
125 |
139 | ```
140 |
141 | ## API
142 | ### injector
143 | The injector is used to register dependencies that can then be injected into a component.
144 | ```javascript
145 | var injector = require('vue-inject');
146 | ```
147 | The injector must be regstered on the Vue class:
148 | ```javascript
149 | Vue.use(injector);
150 | ```
151 | You can supply an options object to determine which properties will be injected.
152 | ```js
153 | Vue.use(injector, { dependencies: true, mixins: true, directives: true, components: true });
154 | ```
155 | By default only `dependencies` is enabled.
156 |
157 | #### service(name, [dependencies], constructor)
158 | Registers a service. A service takes a constructor function (or an ES6 class). When a service is injected into a component, the constructor is instantiated.
159 | The dependencies option determine which dependencies to inject into the constructor. These will be passed into the function in the same order.
160 | ```javascript
161 | injector.service('myService', ['injected'], function(injected){
162 | this.foo = () => {};
163 | });
164 | ```
165 |
166 | #### factory(name, [dependencies], function)
167 | Registers a factory. When injected, the function is called and the return value is then passed into the component.
168 | Similarly to the `service` type, any dependencies are injected into the function parameters.
169 | ```javascript
170 | injector.factory('myFactory', ['injected'], function(injected){
171 | return {
172 | foo : () => {}
173 | };
174 | });
175 | ```
176 |
177 | #### constant(name, value);
178 | Registers a constant value.
179 | ```javascript
180 | injector.constant('myConstant', { foo : 'bah' })
181 | ```
182 |
183 | #### decorator(name, function)
184 | Registers a decorator. A decorator allows you to modify an existing factory. The return value of the function is then used as the resolved factory.
185 | ```javascript
186 | injector.decorator('myFactory', function (myFactory) {
187 | myFactory.foo = 'decorated';
188 | return myFactory;
189 | });
190 | ```
191 |
192 | #### get(name, [namedDependencies])
193 | Component dependencies are calculated automatically, however, there may be times when you want to use a dependency outside of a component. This allows you to pull a dependency directory from the injector. The value is resolved in exactly the same way.
194 | ```javascript
195 | var myService = injector.get('myService');
196 | myService.doStuff();
197 | ```
198 | The namedDependencies parameter accepts an object with custom values for a dependency. For example: if your factory depends on an *apiUrl* constant, you can overwrite the value of that constant by passing in a new value.
199 | ```javascript
200 | var myService = injector.get('myService', { apiUrl : 'localhost:3000/' });
201 | myService.createUrl('foo') === 'localhost:3000/foo';
202 | ```
203 |
204 | #### reset()
205 | Removes all registered factories from the injector.
206 |
207 | #### clearCache()
208 | Once a dependency has been injected, its value is cached (see *lifecycles* below). Usually this is fine, as most factories will be stateless. However sometimes it is necessary to recalculate a factory, such as when unit testing. Clear cache sets all registered factories to an unresolved state. The next time they are injected, the values will be recalculated.
209 | ```javascript
210 | injector.factory('obj', () => { return {}; });
211 |
212 | var a = injector.get('obj');
213 | var b = injector.get('obj');
214 | a === b; // true
215 |
216 | injector.clearCache();
217 |
218 | var c = injector.get('obj');
219 | a === c; // false
220 | ```
221 |
222 | #### spawn(extend)
223 | If you have multiple Vue applications, you can create a new injector using `spawn`.
224 | ```javascript
225 | let injector2 = injector.spawn();
226 | ```
227 | By default this will create a brand new injector, but if you want to share registered services/factories between the two, pass `true` into the function. This will create a new injector that *inherits* the previous one. Any factories registered on the first injector will be available to the second, but not vice versa.
228 |
229 | #### strict
230 | If set to `false` then all dependencies will be made optional. If a component's dependency cannot be found, rather than throwing an error it will just be set to `undefined`. Not that this does not affect the `get` function.
231 |
232 | #### encase
233 | ```js
234 | (
235 | dependencies?: Array,
236 | fn: (...deps) => Function
237 | )
238 | ```
239 | Encase allows you to wrap any function in an outer function. This allows you to inject dependencies (at runtime) but the original function signature will remain the same.
240 |
241 | In the context of a Vue application, this method comes into its own when writnig Vuex actions. For example, the following action:
242 |
243 | ```js
244 | import axios from 'axios';
245 |
246 | const actions = {
247 | FETCH: ({ commit }) => {
248 | axios.get('/my/api').then((response) => {
249 | commit('FETCHED', response.data);
250 | });
251 | },
252 | };
253 | ```
254 | can be rewritten as:
255 | ```js
256 | const actions = {
257 | FETCH: encase([ 'axios' ], (axios) => ({ commit }) => {
258 | axios.get('/my-api').then((response) => {
259 | commit('FETCHED', response.data);
260 | });
261 | }),
262 | };
263 | ```
264 | now this does add a little more code to the function, but it means we've got proper dependency injection per action! And Vuex doesn't even need to know about it.
265 |
266 | ### Lifecycle
267 | When registering a factory or service, it's possible to determine the lifecycle.
268 | *As of v0.4, the default lifecycle is set to `class`*.
269 |
270 | #### application
271 | Caches the value the first time the factory is injected. This value is then re-used every time.
272 | ```javascript
273 | injector.factory('myFactory', fn).lifecycle.application();
274 | ```
275 | #### none
276 | Never caches the value. Every time the factory is injected, the value is recalculated.
277 | ```javascript
278 | injector.factory('myFactory', fn).lifecycle.none();
279 | ```
280 | #### class
281 | Caches the value against the current injector. If you have multiple injectors, the value will be cached against the current injector only, any other injectors will have to recalculate its value.
282 | ```javascript
283 | injector.factory('myFactory', fn).lifecycle.class();
284 | ```
285 |
286 | ### Injecting into Components
287 | There are number of ways you can inject a dependency into a Vue component:
288 | #### dependencies
289 | The most common way is to declare your dependencies on the component:
290 | ```javascript
291 | export default {
292 | data(){},
293 | computed : {},
294 | methods : {},
295 | dependencies : ['myFactory']
296 | };
297 | ```
298 | The `dependencies` property accepts either a string, an array, or an object:
299 | ##### array
300 | For each string in the array, the injector will find the corresponding factory and inject it into the component.
301 | ```javascript
302 | dependencies : ['dep1', 'dep2', 'dep3']
303 | ```
304 | ##### string
305 | This is the same as supplying an array with a single element.
306 | ##### object
307 | An object allows you to specify an alias for a factory.
308 | ```javascript
309 | dependencies : { myAlias : 'myFactory' }
310 | ```
311 | then in your component you can access the injected *myFactory* instance via `this.myAlias`.
312 |
313 | > For the following methods, you must enable their related options when calling Vue.use
314 |
315 | #### components
316 | If you register components on the injector you can then inject them into the components property:
317 | ```javascript
318 | components : { myComponent : 'injectedComponent' }
319 | ```
320 | #### directives
321 | The same is true for directives:
322 | ```javascript
323 | directives : { myDirective : 'injectedDirective' }
324 | ```
325 | #### prototype
326 | You can also add a `dependencies` object to Vue's `prototype`. These dependencies will then be injected into every component.
327 | ```javascript
328 | Vue.prototype.dependencies = ['myService'];
329 | ```
330 |
331 | ### factories
332 | The injector comes bundled with [jpex-web](https://www.npmjs.com/package/jpex-web) and [jpex-defaults](https://www.npmjs.com/package/jpex-defaults), meaning that you have access to the following factories automatically:
333 |
334 | #### $copy
335 | Performs either a deep or shallow copy of an object.
336 | ```javascript
337 | $copy(obj); // shallow copy
338 | $copy.shallow(obj); // shallow copy
339 | $copy.deep(obj); // deep copy
340 | $copy.extend(newObj, obj, obj2); // Deep copies obj2 and obj into newObj.
341 | ```
342 | #### $typeof
343 | Returns the type of an object. This goes further than `typeof` in that it differentiates between objects, arrays, dates, regular expressions, null, etc.
344 | ```javascript
345 | var t = $typeof('hw'); // 'string'
346 | ```
347 | #### $log
348 | Wraps the console functions.
349 | ```javascript
350 | $log('log');
351 | $log.log('log');
352 | $log.warn('warning');
353 | $log.error('Error');
354 | ```
355 | #### $promise
356 | Wraps up the Promise class and includes a simple polyfill implementation if needed. It exposes all of Promise's static methods i.e. `resolve`, `reject`, `all`, and `race`.
357 | ```javascript
358 | return $promise((resolve, reject) => {});
359 | ```
360 | #### $resolve
361 | Equivalent of `injector.get()`.
362 | #### $timeout
363 | Equivalent of `setTimeout()`.
364 | #### $interval
365 | Equivalent of `setInterval()`.
366 | #### $immediate
367 | Equivalent of `setImmediate()` or `setTimeout(fn, 0)`.
368 | #### $window
369 | Injects the global window object.
370 | #### $document
371 | Injectors the global document object;
372 |
373 | ## Manually extending vue-inject
374 | As **vue-inject** is built from [jpex](https://www.npmjs.com/package/jpex), the Jpex API is fully available for additional configuration. You can even manually create an injector with custom settings using Jpex's `extend` method:
375 | ```javascript
376 | const injector = vueInject.extend({
377 | defaultLifecycle : 1 // application
378 | // more config options...
379 | });
380 | ```
381 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | ## 2.1.0
2 | - Added `encase` method
3 |
4 | ## 2.0.1
5 | - Fixed an issue where doing `Vue.extend().use(vueInject)` meant vue inject could not find `optionMergeStrategies`
6 |
7 | ## 2.0.0
8 | - Injecting components/mixins/directives is now optional
9 | - There is now a default merging strategy for when using mixins with dependencies
10 | - Added a `injector.strict` option which makes all dependencies optional. Note this doesn't affect `injector.get`
11 |
12 | ## 1.0.0
13 | - Upgraded to Jpex 2.0.0
14 | - Methods like `clearCache` and `get` now mirror Jpex's `$clearCache` and `$resolve` methods.
15 | - Added decorators
16 | - Removed enum and interface
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-inject",
3 | "version": "2.1.1",
4 | "description": "Dependency Injection for Vue",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "ava",
8 | "watch": "ava --watch",
9 | "coverage": "nyc npm test",
10 | "lint": "eslint ./src",
11 | "publish-please": "publish-please",
12 | "prepublishOnly": "publish-please guard",
13 | "build": "node node_modules/jpex-build-tools/build --entry ./src --output ./dist/vue-inject.js --name injector && node node_modules/jpex-build-tools/build --entry ./src --output ./dist/vue-inject.min.js --name injector --minify",
14 | "lcov-fix": "node node_modules/jpex-build-tools/lcov-fix",
15 | "travis-prepublish": "node node_modules/jpex-build-tools/travis-prepublish"
16 | },
17 | "ava": {
18 | "files": [
19 | "spec/**/*.spec.js"
20 | ],
21 | "source": [
22 | "src/**/*.js"
23 | ],
24 | "require": [],
25 | "verbose": false,
26 | "concurrency": 20
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/jpex-js/vue-inject.git"
31 | },
32 | "author": "Jack Ellis",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/jpex-js/vue-inject/issues"
36 | },
37 | "homepage": "https://github.com/jpex-js/vue-inject#readme",
38 | "dependencies": {
39 | "jpex": "^2.1.0",
40 | "jpex-web": "^2.0.0"
41 | },
42 | "devDependencies": {
43 | "ava": "^0.18.2",
44 | "eslint": "^3.18.0",
45 | "jpex-build-tools": "github:jpex-js/jpex-build-tools",
46 | "nyc": "^10.1.2",
47 | "publish-please": "^2.3.0",
48 | "sinon": "^2.1.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/spec/injector.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import vueInject from '../src';
3 |
4 | test.beforeEach(function (t) {
5 | let injector = vueInject.extend();
6 |
7 | t.context = {injector};
8 | });
9 |
10 | // get
11 | test('get is an alias for $resolve', function (t) {
12 | let {injector} = t.context;
13 |
14 | t.is(injector.get, injector.$resolve);
15 | });
16 | test('get resolves dependencies', function (t) {
17 | let {injector} = t.context;
18 |
19 | injector.register.factory('myFactory', () => 'foo');
20 | let resolved = injector.get('myFactory');
21 |
22 | t.is(resolved, 'foo');
23 | });
24 | test('uses named dependencies', function (t) {
25 | let {injector} = t.context;
26 |
27 | let resolved = injector.get('named', {named : 'bah'});
28 |
29 | t.is(resolved, 'bah');
30 | });
31 | test('has jpex-web factories', function (t) {
32 | let {injector} = t.context;
33 |
34 | let $promise = injector.get('$promise');
35 |
36 | return $promise(resolve => { resolve(); });
37 | });
38 |
39 | // reset
40 | test('reset deletes all registered services', function (t) {
41 | let {injector} = t.context;
42 |
43 | injector.factory('myFactory', () => 'foo');
44 |
45 | t.notThrows(() => injector.get('myFactory'));
46 | t.notThrows(() => injector.get('$promise'));
47 |
48 | injector.reset();
49 |
50 | t.throws(() => injector.get('myFactory'));
51 | t.notThrows(() => injector.get('$promise'));
52 | });
53 |
54 | // spawn
55 | test('spawn creates a new injector', function (t) {
56 | let {injector} = t.context;
57 | let i2 = injector.spawn();
58 |
59 | t.not(i2, undefined);
60 | t.not(i2.get, undefined);
61 | t.not(i2.reset, undefined);
62 | });
63 | test('spawn does not share parent factories', function (t) {
64 | let {injector} = t.context;
65 | injector.factory('myFactory', () => 'foo');
66 |
67 | let i2 = injector.spawn();
68 |
69 | t.notThrows(() => injector.get('myFactory'));
70 | t.throws(() => i2.get('myFactory'));
71 | });
72 | test('spawn inherits parent factories if true', function (t) {
73 | let {injector} = t.context;
74 | injector.factory('myFactory', () => 'foo');
75 |
76 | let i2 = injector.spawn(true);
77 |
78 | t.notThrows(() => injector.get('myFactory'));
79 | t.notThrows(() => i2.get('myFactory'));
80 | });
81 |
82 | // clearCache
83 | test('clearCache clears the cache', function (t) {
84 | let {injector} = t.context;
85 | injector.factory('myFactory', () => ({}));
86 |
87 | let a = injector.get('myFactory');
88 | let b = injector.get('myFactory');
89 |
90 | // clear a specific factory
91 | injector.clearCache('myService');
92 |
93 | let c = injector.get('myFactory');
94 |
95 | // clear all factories
96 | injector.clearCache();
97 |
98 | let d = injector.get('myFactory');
99 |
100 | t.is(a, b);
101 | t.is(a, c);
102 | t.not(a, d);
103 | });
104 |
--------------------------------------------------------------------------------
/spec/install.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import Sinon from 'sinon';
3 | import Vue from './vue';
4 | import vueInject from '../src';
5 |
6 | test.beforeEach(function (t) {
7 | let vue = Vue();
8 | let injector = vueInject.extend();
9 | let sinon = Sinon.sandbox.create();
10 |
11 | injector.factory('factory', () => 'resolved!');
12 |
13 | t.context = {vue, injector, sinon};
14 | });
15 |
16 | test('applies a vue mixin', function (t) {
17 | let {injector, vue, sinon} = t.context;
18 |
19 | sinon.stub(vue, 'mixin');
20 |
21 | vue.use(injector);
22 |
23 | t.true(vue.mixin.called);
24 | });
25 |
26 | test('resolves vue dependencies', function (t) {
27 | let {injector, vue} = t.context;
28 |
29 | vue.dependencies = ['factory'];
30 |
31 | vue.use(injector);
32 |
33 | t.is(vue.factory, 'resolved!');
34 | });
35 |
36 | test('resolves component dependencies', function (t) {
37 | let {injector, vue} = t.context;
38 |
39 | vue.$options.dependencies = 'factory';
40 |
41 | vue.use(injector);
42 |
43 | t.is(vue.factory, 'resolved!');
44 | });
45 |
46 | test('resolves aliases', function (t) {
47 | let {injector, vue} = t.context;
48 |
49 | vue.dependencies = {alias : 'factory'};
50 |
51 | vue.use(injector);
52 |
53 | t.is(vue.alias, 'resolved!');
54 | });
55 |
56 | test('resolves components', function (t) {
57 | let {injector, vue} = t.context;
58 |
59 | vue.$options.components = {test : 'factory'};
60 |
61 | vue.use(injector, { components: true });
62 |
63 | t.is(vue.$options.components.test, 'resolved!');
64 | });
65 | test('ignores real components', function (t) {
66 | let {injector, vue} = t.context;
67 | let real = {template : ''};
68 |
69 | vue.$options.components = {real, fake : 'factory'};
70 |
71 | vue.use(injector, { components: true });
72 |
73 | t.is(vue.$options.components.real, real);
74 | t.is(vue.$options.components.fake, 'resolved!');
75 | });
76 |
77 | test('resolves mixins', function (t) {
78 | let {injector, vue} = t.context;
79 |
80 | vue.$options.mixins = {factory : 'factory'};
81 |
82 | vue.use(injector, { mixins: true });
83 |
84 | t.is(vue.$options.mixins.factory, 'resolved!');
85 | });
86 |
87 | test('resolves directives', function (t) {
88 | let {injector, vue} = t.context;
89 |
90 | vue.$options.directives = {factory : 'factory'};
91 |
92 | vue.use(injector, { directives: true });
93 |
94 | t.is(vue.$options.directives.factory, 'resolved!');
95 | });
96 |
97 | test('has $context constant', function (t) {
98 | let {injector, vue} = t.context;
99 |
100 | vue.dependencies = {self : '$context'};
101 |
102 | vue.use(injector);
103 |
104 | t.is(vue.self, vue);
105 | });
106 |
107 | test('resolves factories as singletons', function (t) {
108 | let {injector, vue} = t.context;
109 |
110 | injector.factory('factory', () => ({}));
111 |
112 | vue.dependencies = {a : 'factory', b : 'factory'};
113 |
114 | vue.use(injector);
115 |
116 | t.not(vue.a, undefined);
117 | t.is(vue.a, vue.b);
118 |
119 | const vue2 = Vue();
120 | vue2.dependencies = {a : 'factory', b : 'factory'};
121 |
122 | vue2.use(injector);
123 |
124 | t.is(vue2.a, vue.a);
125 | t.is(vue2.b, vue.a);
126 | });
127 |
128 | test('sets option merging strategies', function (t) {
129 | let {injector, vue} = t.context;
130 |
131 | vue.use(injector);
132 |
133 | t.is(typeof vue.config.optionMergeStrategies.dependencies, 'function');
134 |
135 | const merged = vue.config.optionMergeStrategies.dependencies(['apple'], { banana: 'b'});
136 |
137 | t.deepEqual(merged, [ 'apple', { banana: 'b'}]);
138 | });
139 |
140 | // strict
141 | test('strict throws when a dep is not found', function (t) {
142 | let {injector, vue} = t.context;
143 |
144 | vue.dependencies = 'foofactory';
145 |
146 | t.throws(() => vue.use(injector));
147 | t.throws(() => injector.get('foofactory'));
148 | });
149 | test('non-strict does not throw for missing deps', function (t) {
150 | let {injector, vue} = t.context;
151 |
152 | vue.dependencies = 'foofactory';
153 | injector.strict = false;
154 |
155 | t.notThrows(() => vue.use(injector));
156 | t.throws(() => injector.get('foofactory'));
157 | });
158 |
--------------------------------------------------------------------------------
/spec/register.spec.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import vueInject from '../src';
3 |
4 | test.beforeEach(function (t) {
5 | let injector = vueInject.extend();
6 |
7 | t.context = {injector};
8 | });
9 |
10 | test('factory is register.factory alias', function (t) {
11 | let {injector} = t.context;
12 | t.is(injector.factory, injector.register.factory);
13 | });
14 | test('registers a factory', function (t) {
15 | let {injector} = t.context;
16 | injector.factory('foo', () => 'oof');
17 |
18 | t.is(injector.get('foo'), 'oof');
19 | });
20 |
21 | test('service is register.service alias', function (t) {
22 | let {injector} = t.context;
23 | t.is(injector.service, injector.register.service);
24 | });
25 | test('registers a service', function (t) {
26 | let {injector} = t.context;
27 | injector.service('foo', function () {
28 | this.foo = 'foo';
29 | });
30 |
31 | t.is(injector.get('foo').foo, 'foo');
32 | });
33 |
34 | test('constant is register.constant alias', function (t) {
35 | let {injector} = t.context;
36 | t.is(injector.constant, injector.register.constant);
37 | });
38 | test('registers a constant', function (t) {
39 | let {injector} = t.context;
40 | injector.constant('foo', 'foo');
41 |
42 | t.is(injector.get('foo'), 'foo');
43 | });
44 |
45 | test('decorator is register.decorator alias', function (t) {
46 | let {injector} = t.context;
47 | t.is(injector.decorator, injector.register.decorator);
48 | });
49 | test('registers a decorator', function (t) {
50 | let {injector} = t.context;
51 | injector.factory('factory', () => 'simple');
52 | injector.decorator('factory', factory => factory.split('').reverse().join(''));
53 |
54 | t.is(injector.get('factory'), 'elpmis');
55 | });
56 |
--------------------------------------------------------------------------------
/spec/vue.js:
--------------------------------------------------------------------------------
1 | // var Vue = {
2 | // use(plugin, options){
3 | // plugin.install(this, options);
4 | // },
5 | // mixin(config){
6 | // Object.keys(config).forEach(key => {
7 | // config[key].call(this);
8 | // });
9 | // },
10 | // $options : {}
11 | // };
12 |
13 | module.exports = function () {
14 | var Vue = {
15 | use(plugin, options){
16 | plugin.install(this, options);
17 | },
18 | mixin(config){
19 | Object.keys(config).forEach(key => {
20 | config[key].call(this);
21 | });
22 | },
23 | $options : {},
24 | config: {
25 | optionMergeStrategies: {},
26 | },
27 | };
28 | return Vue;
29 | };
30 |
--------------------------------------------------------------------------------
/src/base.js:
--------------------------------------------------------------------------------
1 | var Jpex = require('jpex/src');
2 | var jpexConstants = require('jpex/src/constants');
3 | var plugin = require('./plugin');
4 | var install = require('./install');
5 |
6 | var Base = module.exports = Jpex.extend({
7 | config : {
8 | defaultLifecycle : jpexConstants.CLASS
9 | }
10 | });
11 |
12 | if (!Base.$$using['jpex-web']){
13 | Base.use(require('jpex-web'));
14 | }
15 |
16 | Base.use(plugin);
17 |
18 | // Delete all registered factories
19 | Base.reset = function () {
20 | var self = this;
21 | this.clearCache();
22 | Object.keys(this.$$factories)
23 | .forEach(function (key) {
24 | delete self.$$factories[key];
25 | });
26 | };
27 |
28 | // Create a new injector
29 | Base.spawn = function (extend) {
30 | if (extend){
31 | return this.extend();
32 | }else{
33 | return Base.extend();
34 | }
35 | };
36 |
37 | // Allow setting a soft mode
38 | Base.strict = true;
39 |
40 | // Install method used by Vue.use
41 | Base.install = install;
42 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./base').extend();
2 |
--------------------------------------------------------------------------------
/src/install.js:
--------------------------------------------------------------------------------
1 | module.exports = function (Vue, options) {
2 | options = options || {};
3 | var self = this;
4 |
5 | var $typeof = this.$resolve('$typeof');
6 | var strict = this.strict;
7 |
8 | function fixName(name) {
9 | if (strict) {
10 | return name;
11 | }
12 | if (name.charAt(0) === '_' && name.substr(name.length -1) === '_') {
13 | return name;
14 | }
15 | return '_' + name + '_';
16 | }
17 |
18 | function setProperty(target, name, value) {
19 | Object.defineProperty(target, name, {
20 | enumerable : true,
21 | configurable : true,
22 | writable : true,
23 | value : value
24 | });
25 | }
26 |
27 | function resolveToTarget(dependencies, target, named) {
28 | if (!dependencies){
29 | return;
30 | }
31 |
32 | [].concat(dependencies)
33 | .forEach(function (dependency) {
34 | switch ($typeof(dependency)){
35 | case 'string': // resolve dependency and attach to the same-named property
36 | setProperty(target, dependency, self.$resolve(fixName(dependency), named));
37 | break;
38 | case 'object': // resolve each property and use the key as the property name
39 | // Aliases
40 | Object.keys(dependency)
41 | .forEach(function (key) {
42 | var value = dependency[key];
43 | if ($typeof(value) === 'string'){
44 | setProperty(target, key, self.$resolve(fixName(value), named));
45 | }
46 | });
47 | break;
48 | }
49 | });
50 | }
51 |
52 | function getOptionMergeStrategies(Vue) {
53 | while (Vue && !Vue.config) {
54 | Vue = Vue.super;
55 | }
56 | return (Vue && Vue.config && Vue.config.optionMergeStrategies) || {};
57 | }
58 |
59 | var mergeStrategies = getOptionMergeStrategies(Vue);
60 | mergeStrategies.dependencies = mergeStrategies.depnedencies || function (toVal, fromVal) {
61 | if (!toVal) {
62 | return fromVal;
63 | }
64 | if (!fromVal) {
65 | return toVal;
66 | }
67 | return [].concat(toVal).concat(fromVal).filter(function(v, i, a) { return a.indexOf(v) === i; });
68 | };
69 |
70 | Vue.mixin({
71 | beforeCreate : function () {
72 | var named = { $context : this };
73 | if (options.dependencies !== false) {
74 | resolveToTarget(this.dependencies, this, named);
75 | resolveToTarget(this.$options.dependencies, this, named);
76 | }
77 | if (options.components) {
78 | resolveToTarget(this.$options.components, this.$options.components, named);
79 | }
80 | if (options.mixins) {
81 | resolveToTarget(this.$options.mixins, this.$options.mixins, named);
82 | }
83 | if (options.directives) {
84 | resolveToTarget(this.$options.directives, this.$options.directives, named);
85 | }
86 | }
87 | });
88 | };
89 |
--------------------------------------------------------------------------------
/src/plugin.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name : 'vue-inject',
3 | install : function (o) {
4 | o.on('extend', function (payload) {
5 | Object.defineProperties(payload.Class, {
6 | get : {
7 | enumerable : true,
8 | configurable : true,
9 | get : function () {
10 | return this.$resolve;
11 | }
12 | },
13 | clearCache : {
14 | enumerable : true,
15 | configurable : true,
16 | get : function () {
17 | return this.$clearCache;
18 | }
19 | },
20 | encase: {
21 | enumerable: true,
22 | configurable: true,
23 | get: function () {
24 | return this.$encase;
25 | }
26 | },
27 | factory : {
28 | enumerable : true,
29 | configurable : true,
30 | get : function () {
31 | return this.register.factory;
32 | }
33 | },
34 | service : {
35 | enumerable : true,
36 | configurable : true,
37 | get : function () {
38 | return this.register.service;
39 | }
40 | },
41 | constant : {
42 | enumerable : true,
43 | configurable : true,
44 | get : function () {
45 | return this.register.constant;
46 | }
47 | },
48 | decorator : {
49 | enumerable : true,
50 | configurable : true,
51 | get : function () {
52 | return this.register.decorator;
53 | }
54 | },
55 | });
56 | });
57 | }
58 | };
59 |
--------------------------------------------------------------------------------