├── .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 | [![npm version](https://badge.fury.io/js/vue-inject.svg)](https://badge.fury.io/js/vue-inject) 5 | [![Build Status](https://travis-ci.org/jpex-js/vue-inject.svg?branch=master)](https://travis-ci.org/jpex-js/vue-inject) 6 | [![Code Climate](https://codeclimate.com/github/jpex-js/vue-inject/badges/gpa.svg)](https://codeclimate.com/github/jpex-js/vue-inject) 7 | [![Test Coverage](https://codeclimate.com/github/jpex-js/vue-inject/badges/coverage.svg)](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 | 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 | --------------------------------------------------------------------------------