├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── __tests__ ├── .eslintrc ├── expose.test.js └── inject.test.js ├── package.json └── src ├── expose.js ├── index.js ├── inject.js └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "transform-object-rest-spread" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This file is for unifying the coding style for different editors and IDEs. 2 | ; More information at http://editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | indent_size = 4 9 | indent_style = space 10 | end_of_line = lf 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | 14 | [{package.json,*.scss,*.css}] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "spatie", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | node_modules 3 | npm-debug.log 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __tests__ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All Notable changes to `vue-expose-inject` will be documented in this file 4 | 5 | ## 1.0.0 6 | - Initial release 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Spatie bvba 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 13 | > all 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 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [](https://supportukrainenow.org) 3 | 4 | # A React context-like solution for Vue.js 5 | 6 | [![Latest Version on NPM](https://img.shields.io/npm/v/vue-expose-inject.svg?style=flat-square)](https://npmjs.com/package/vue-expose-inject) 7 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) 8 | [![Build Status](https://img.shields.io/travis/spatie/vue-expose-inject/master.svg?style=flat-square)](https://travis-ci.org/spatie/vue-expose-inject) 9 | 10 | **This package is deprecated! Vue has this functionality built in as of v2.2** 11 | 12 | Exposes a set of properties to all of a components descendants. 13 | 14 | ```js 15 | // Expose a property... 16 | const vm = new Vue({ 17 | mixins: [expose], 18 | 19 | data: () => ({ 20 | bus: new Bus(), 21 | }), 22 | 23 | expose() { 24 | return { 25 | bus: this.bus, 26 | }; 27 | }, 28 | }); 29 | 30 | // ...to be able to inject it in a child component 31 | const child = new Vue({ 32 | parent: vm, 33 | 34 | computed: { 35 | ...inject(['bus']), 36 | }, 37 | }); 38 | 39 | child.bus; // EventBus instance 40 | ``` 41 | 42 | ## Support us 43 | 44 | [](https://spatie.be/github-ad-click/vue-expose-inject) 45 | 46 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 47 | 48 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 49 | 50 | ## Postcardware 51 | 52 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 53 | 54 | You're free to use this package (it's [MIT-licensed](LICENSE.md)), but if it makes it to your production environment we'd appreciate if you send us a postcard from your hometown, mentioning which of our package(s) you are using. 55 | 56 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 57 | 58 | The best postcards will get published on the open source page on our website. 59 | 60 | ## Install 61 | 62 | You can install the package via yarn: 63 | 64 | ```bash 65 | yarn add vue-expose-inject 66 | ``` 67 | 68 | ## Use Cases 69 | 70 | This package is based on React's [context](https://facebook.github.io/react/docs/context.html) feature. Exposes and inject are useful for giving your components access to global-ish objects, like event busses or authentication data. Expose/inject can make your application harder to reason about, and depends on a certain hierarchy with your components, so use with care! 71 | 72 | ## Usage 73 | 74 | Child components can inject properties that are exposed by one of their ancestors. This goes beyond parent-child communitation, the distance between the parent and child, grandchild, etc. doesn't matter. 75 | 76 | To get started, expose an object from a parent component by adding the `expose` mixin and an `expose` function, which returns the object: 77 | 78 | ```js 79 | // Parent.js 80 | 81 | import { expose } from 'vue-expose-inject'; 82 | 83 | export default { 84 | mixins: [expose], 85 | 86 | expose() { 87 | return { 88 | bus: new Bus(), 89 | }; 90 | }, 91 | } 92 | ``` 93 | 94 | Vue instance properties can also be exposed by passing their names in an array: 95 | 96 | ```js 97 | export default { 98 | mixins: [expose], 99 | 100 | data: () => ({ 101 | bus: new Bus(), 102 | }), 103 | 104 | expose: ['bus'], 105 | } 106 | ``` 107 | 108 | Descendant components can then inject the property using the `inject` helper function, which uses the same syntax as Vuex's `map` helpers: 109 | 110 | ```js 111 | // Child.js 112 | 113 | import { inject } from 'vue-expose-inject'; 114 | 115 | export default { 116 | parent: vm, 117 | 118 | computed: { 119 | ...inject(['bus']), 120 | }, 121 | } 122 | ``` 123 | 124 | > If you try to inject a property that hasn't been exposed by an ancestor, an error gets thrown 125 | 126 | Injected properties can be renamed by passing in an object instead of an array: 127 | 128 | ```js 129 | export default { 130 | // ... 131 | 132 | computed: { 133 | ...inject({ 134 | myBus: 'bus', 135 | }), 136 | }, 137 | } 138 | ``` 139 | 140 | If you're not using the spread operator, you can `assign` the properties: 141 | 142 | ```js 143 | export default { 144 | // ... 145 | 146 | computed: Object.assign(inject(['bus']), { 147 | // My computed properties... 148 | }), 149 | } 150 | ``` 151 | 152 | ## Change log 153 | 154 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 155 | 156 | ## Testing 157 | 158 | ``` bash 159 | $ npm run test 160 | ``` 161 | 162 | ## Contributing 163 | 164 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 165 | 166 | ## Security 167 | 168 | If you discover any security related issues, please contact [Sebastian De Deyne](https://github.com/sebastiandedeyne) instead of using the issue tracker. 169 | 170 | ## Credits 171 | 172 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 173 | - [All Contributors](../../contributors) 174 | 175 | ## About Spatie 176 | Spatie is a webdesign agency based in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). 177 | 178 | ## License 179 | 180 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 181 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } -------------------------------------------------------------------------------- /__tests__/expose.test.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { expose } from '../src'; 3 | 4 | Vue.config.errorHandler = (err, vm, info) => { 5 | vm._error = err; 6 | }; 7 | 8 | describe('expose', () => { 9 | it('can expose any value', () => { 10 | const vm = new Vue({ 11 | mixins: [expose], 12 | data() { 13 | return { 14 | myOtherProp: 'bar', 15 | }; 16 | }, 17 | expose() { 18 | return { 19 | myProp: 'foo', 20 | myOtherProp: this.myOtherProp, 21 | }; 22 | }, 23 | }); 24 | 25 | expect(vm.$exposed).toEqual({ myProp: 'foo', myOtherProp: 'bar' }); 26 | }); 27 | 28 | it('can expose an array of named properties', () => { 29 | const vm = new Vue({ 30 | mixins: [expose], 31 | data() { 32 | return { 33 | myProp: 'foo', 34 | }; 35 | }, 36 | expose: ['myProp'], 37 | }); 38 | 39 | expect(vm.$exposed).toEqual({ myProp: 'foo' }); 40 | }); 41 | 42 | it('throws an error if a property that doesn\'t exist gets exposed', () => { 43 | const vm = new Vue({ 44 | mixins: [expose], 45 | expose: ['myProp'], 46 | }); 47 | 48 | expect(vm._error).toBeDefined(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /__tests__/inject.test.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { expose, inject } from '../src'; 3 | 4 | describe('inject', () => { 5 | 6 | it('can inject a property that\'s set on a parent', () => { 7 | const parent = new Vue({ 8 | mixins: [expose], 9 | expose() { 10 | return { 11 | propA: 'foo', 12 | }; 13 | }, 14 | }); 15 | 16 | const child = new Vue({ 17 | parent, 18 | computed: { 19 | ...inject(['propA']), 20 | }, 21 | }); 22 | 23 | expect(child.propA).toBe('foo'); 24 | }); 25 | 26 | it('can inject multiple properties', () => { 27 | const parent = new Vue({ 28 | mixins: [expose], 29 | expose() { 30 | return { 31 | propA: 'foo', 32 | propB: 'bar', 33 | }; 34 | }, 35 | }); 36 | 37 | const child = new Vue({ 38 | parent, 39 | computed: { 40 | ...inject(['propA', 'propB']), 41 | }, 42 | }); 43 | 44 | expect(child.propA).toBe('foo'); 45 | expect(child.propB).toBe('bar'); 46 | }); 47 | 48 | it('can rename injected properties', () => { 49 | const parent = new Vue({ 50 | mixins: [expose], 51 | expose() { 52 | return { 53 | propA: 'foo', 54 | }; 55 | }, 56 | }); 57 | 58 | const child = new Vue({ 59 | parent, 60 | computed: { 61 | ...inject({ 62 | myProp: 'propA', 63 | }), 64 | }, 65 | }); 66 | 67 | expect(child.myProp).toBe('foo'); 68 | }); 69 | 70 | it('can inject a property that\'s set on a further ancestor', () => { 71 | const grandParent = new Vue({ 72 | mixins: [expose], 73 | expose() { 74 | return { 75 | propA: 'foo', 76 | }; 77 | }, 78 | }); 79 | 80 | const parent = new Vue({ 81 | parent: grandParent, 82 | }); 83 | 84 | const child = new Vue({ 85 | parent, 86 | computed: { 87 | ...inject(['propA']), 88 | }, 89 | }); 90 | 91 | expect(child.propA).toBe('foo'); 92 | }); 93 | 94 | it('returns the last exposed property if there are multiple with the same name', () => { 95 | const grandParent = new Vue({ 96 | mixins: [expose], 97 | expose() { 98 | return { 99 | propA: 'foo', 100 | }; 101 | }, 102 | }); 103 | 104 | const parent = new Vue({ 105 | parent: grandParent, 106 | mixins: [expose], 107 | expose() { 108 | return { 109 | propA: 'bar', 110 | }; 111 | }, 112 | }); 113 | 114 | const child = new Vue({ 115 | parent, 116 | computed: { 117 | ...inject(['propA']), 118 | }, 119 | }); 120 | 121 | expect(child.propA).toBe('bar'); 122 | }); 123 | 124 | it.only('can inject a property that was exposed as a symbol', () => { 125 | const myProp = Symbol(); 126 | 127 | const parent = new Vue({ 128 | mixins: [expose], 129 | expose() { 130 | return { 131 | [myProp]: 'foo', 132 | }; 133 | }, 134 | }); 135 | 136 | const child = new Vue({ 137 | parent, 138 | computed: { 139 | ...inject({ 140 | myProp, 141 | }), 142 | }, 143 | }); 144 | 145 | expect(child.myProp).toBe('foo'); 146 | }); 147 | 148 | it('throws an error if it tries to inject a property that hasn\'t been exposed', () => { 149 | 150 | expect(() => { 151 | const vm = new Vue({ 152 | computed: { 153 | ...inject(['propA']), 154 | }, 155 | }); 156 | 157 | vm.propA; 158 | }).toThrow(); 159 | }); 160 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-expose-inject", 3 | "version": "1.0.0", 4 | "description": "A React context-like solution for Vue.js", 5 | "main": "dist/index.js", 6 | "jsnext:main": "src/index.js", 7 | "scripts": { 8 | "build": "babel src -d dist", 9 | "lint": "eslint src __tests__ --fix && exit 0", 10 | "prepublish": "npm run build", 11 | "test": "jest" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/spatie/vue-expose-inject.git" 16 | }, 17 | "keywords": [ 18 | "spatie" 19 | ], 20 | "author": "Sebastian De Deyne", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/spatie/vue-expose-inject/issues" 24 | }, 25 | "homepage": "https://github.com/spatie/vue-expose-inject", 26 | "devDependencies": { 27 | "babel-cli": "^6.9.0", 28 | "babel-plugin-transform-object-rest-spread": "^6.16.0", 29 | "babel-preset-es2015": "^6.9.0", 30 | "eslint": "^4.19.1", 31 | "eslint-config-spatie": "^2.0.3", 32 | "jest": "^22.4.3", 33 | "vue": "^2.0.0" 34 | }, 35 | "peerDependencies": { 36 | "vue": "^2.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/expose.js: -------------------------------------------------------------------------------- 1 | import { isArray, isFunction } from './util'; 2 | 3 | function retrieveExposedProperties(vm) { 4 | const expose = vm.$options.expose; 5 | 6 | if (isFunction(expose)) { 7 | return expose.call(vm); 8 | } 9 | 10 | if (isArray(expose)) { 11 | return expose.reduce((expose, property) => { 12 | if (! vm.hasOwnProperty(property)) { 13 | throw new Error(`Can't expose \`${property}\` since it's not set`); 14 | } 15 | 16 | expose[property] = vm[property]; 17 | 18 | return expose; 19 | }, {}); 20 | } 21 | 22 | throw new Error(`\`expose\` must be an array or a factory method, \`${typeof expose}\` given`); 23 | } 24 | 25 | const expose = { 26 | created() { 27 | if (this.$options.expose === undefined) { 28 | return; 29 | } 30 | 31 | this.$exposed = retrieveExposedProperties(this); 32 | }, 33 | }; 34 | 35 | export default expose; 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as expose } from './expose'; 2 | export { default as inject } from './inject'; -------------------------------------------------------------------------------- /src/inject.js: -------------------------------------------------------------------------------- 1 | import { normalizeMap } from './util'; 2 | 3 | function resolveExposedProperty(key, vm) { 4 | 5 | if (key in (vm.$exposed || {})) { 6 | return vm.$exposed[key]; 7 | } 8 | 9 | if (vm.$options.parent) { 10 | return resolveExposedProperty(key, vm.$options.parent); 11 | } 12 | 13 | throw new Error(`Dependency \`${key}\` couldn't be resolved`); 14 | } 15 | 16 | function inject(keys) { 17 | return normalizeMap(keys).reduce((resolvedProperties, { key, property }) => { 18 | resolvedProperties[key] = function () { 19 | return resolveExposedProperty(property, this); 20 | }; 21 | return resolvedProperties; 22 | }, {}); 23 | } 24 | 25 | export default inject; -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | // https://github.com/vuejs/vuex/blob/dae213024b420e611afb53527254177a4b18e857/src/helpers.js#L46 2 | export function normalizeMap(map) { 3 | return Array.isArray(map) 4 | ? map.map(key => ({ key, property: key })) 5 | : Object.keys(map).map(key => ({ key, property: map[key] })); 6 | } 7 | 8 | export function isArray(object) { 9 | return Array.isArray(object); 10 | } 11 | 12 | export function isFunction(object) { 13 | return typeof object === 'function'; 14 | } 15 | --------------------------------------------------------------------------------