├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── example ├── components │ ├── home.vue │ ├── layout.vue │ ├── user-card.vue │ ├── user.vue │ └── users.vue ├── index.html ├── index.js └── routes.js ├── mix-manifest.json ├── package-lock.json ├── package.json ├── src └── resolve.js ├── webpack.mix.js ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-es2015", "babel-preset-minify"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | public/ 3 | .npmignore 4 | mix-manifest.json 5 | webpack.mix.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Vue-resolve 2 | 3 | A VueJS (2.x) / Vue-router plugin that resolves dependencies for routes before entering. 4 | 5 | ### What is this? 6 | 7 | A plugin that reads a `meta.resolve` property on your component's route and resolve it (as a promise) before serving the route. 8 | 9 | **Code Sandbox example** 10 | 11 | https://codesandbox.io/s/wk2k671jyl 12 | 13 | ## Install 14 | 15 | Install from npm: 16 | ```js 17 | npm install vue-resolve --save 18 | ``` 19 | 20 | or from yarn: 21 | ```js 22 | yarn add vue-resolve 23 | ``` 24 | ## Example 25 | 26 | ```js 27 | ... 28 | import VueResolve from 'vue-resolve'; 29 | 30 | const router = new VueRouter({ 31 | routes: [{ 32 | path: '/', 33 | component: mycomp, 34 | meta: { 35 | resolve: { 36 | mydata() { 37 | return axios.get('/api/data'); 38 | } 39 | } 40 | } 41 | }] 42 | }); 43 | 44 | Vue.use(VueResolve, { 45 | router, 46 | dataProperty: 'data', 47 | }); 48 | 49 | new Vue({ 50 | ... 51 | router, 52 | }); 53 | ``` 54 | 55 | **Important:** 56 | 57 | For reactivity please make sure to define the *data* properties your route is going to populate in your component, so Vue can keep track of changes after the routes resolves. (e.g. on the `mycomp` component, *must* exist a `mydata` data) 58 | 59 | For a more complete example, please check the [example folder](/example). 60 | 61 | ### Options 62 | 63 | **resolved()** 64 | 65 | You can add a `resolved()` option in your component that will be executed when dependencies for that route/component are resolved. 66 | 67 | **this.$resolve()** 68 | 69 | You can use `this.$resolve()` to re-resolve dependencies, in case you want to run the same dependencies again without the need of duplicating it. 70 | -------------------------------------------------------------------------------- /example/components/home.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 108 | -------------------------------------------------------------------------------- /example/components/layout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 36 | -------------------------------------------------------------------------------- /example/components/user-card.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | 25 | 40 | -------------------------------------------------------------------------------- /example/components/user.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 26 | 27 | 33 | -------------------------------------------------------------------------------- /example/components/users.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | 39 | 52 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue-Resolve Example Page 8 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue/dist/vue'; 2 | import VueRouter from 'vue-router'; 3 | import VueResolve from '../dist/resolve'; 4 | 5 | import routes from './routes'; 6 | 7 | Vue.use(VueRouter); 8 | 9 | const router = new VueRouter({ 10 | routes, 11 | 12 | scrollBehavior() { 13 | return { x: 0, y: 0 }; 14 | } 15 | }); 16 | 17 | Vue.use(VueResolve, { 18 | router, 19 | dataProperty: 'data', 20 | }); 21 | 22 | new Vue({ 23 | name: 'app', 24 | 25 | router, 26 | 27 | template: ` 28 |
29 |

Vue-Resolve Example

30 | 31 | 32 |
33 | ` 34 | }).$mount('#app'); -------------------------------------------------------------------------------- /example/routes.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import layout from './components/layout.vue'; 4 | import users from './components/users.vue'; 5 | import user from './components/user.vue'; 6 | import home from './components/home.vue'; 7 | 8 | export default [ 9 | { 10 | path: '/', 11 | component: layout, 12 | children: [ 13 | { 14 | path: '/', 15 | component: home, 16 | }, 17 | 18 | { 19 | path: '/users', 20 | component: users, 21 | meta: { 22 | resolve: { 23 | users: _ => axios.get('https://reqres.in/api/users') 24 | } 25 | } 26 | }, 27 | 28 | { 29 | path: '/users/:id', 30 | component: user, 31 | meta: { 32 | resolve: { 33 | user: id => new Promise((resolve) => { 34 | axios.get(`https://reqres.in/api/users/${id}`) 35 | .then(({ data }) => { 36 | resolve(data); 37 | }); 38 | }), 39 | }, 40 | } 41 | } 42 | ] 43 | } 44 | ]; 45 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/public/js/app.js": "/public/js/app.js", 3 | "/public/js/app.js.map": "/public/js/app.js.map", 4 | "/public/index.html": "/public/index.html" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-resolve", 3 | "version": "1.1.5", 4 | "description": "", 5 | "main": "dist/resolve.js", 6 | "author": "Javis Pérez", 7 | "license": "ISC", 8 | "scripts": { 9 | "dev": "NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 10 | "watch": "NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 11 | "hot": "NODE_ENV=development webpack-dev-server --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 12 | "build": "mkdir -p dist && touch dist/resolve.js && babel src/resolve.js --out-file dist/resolve.js", 13 | "build:example": "npm run build && NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 14 | "publish": "npm run build && npm publish" 15 | }, 16 | "devDependencies": { 17 | "axios": "^0.18.0", 18 | "babel-cli": "^6.26.0", 19 | "babel-preset-es2015": "^6.24.1", 20 | "babel-preset-minify": "^0.4.3", 21 | "browser-sync": "^2.24.6", 22 | "browser-sync-webpack-plugin": "^1.2.0", 23 | "cross-env": "^5.1.3", 24 | "eslint": "^3.19.0", 25 | "eslint-config-vue": "^2.0.2", 26 | "eslint-plugin-vue": "^2.1.0", 27 | "laravel-mix": "^2.1.14", 28 | "vue": "^2.5.17", 29 | "vue-template-compiler": "^2.5.17" 30 | }, 31 | "dependencies": { 32 | "vue-router": "^3.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/resolve.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vue-Resolve 3 | * 4 | * Resolve routes dependencies before entering the route. 5 | * 6 | * This plugin let's you define dependencies for your routes (using vue-router) 7 | * that must be resolved before entering the route. 8 | * 9 | * Javis V. Pérez 10 | * http://github.com/javisperez/vue-resolve 11 | */ 12 | export default { 13 | /** 14 | * Install method for Vue as a plugin 15 | * @param {Object} Vue - The Vue instance 16 | * @param {Object} options - User options which are: 17 | * router: The Vue-router instance. 18 | * dataProperty: (Optional) A property of the resolved promise 19 | * that will be evaluated before passing 20 | * to the component. 21 | */ 22 | install(Vue, options = {}) { 23 | // Default values 24 | const defaults = { 25 | dataProperty: null, 26 | }; 27 | 28 | // Merge the given values and the defaults 29 | const userOptions = { 30 | ...defaults, 31 | ...options, 32 | }; 33 | 34 | const { router } = userOptions; 35 | 36 | // Router instance is required 37 | if (!router) { 38 | throw new Error('Vue-resolve needs an instance of the router object.'); 39 | } 40 | 41 | /** 42 | * Resolve Route 43 | * @param {RouteObject} route - A Vue-router RouteObject to get the resolve property if any. 44 | */ 45 | const resolveRoute = (route) => { 46 | /** 47 | * Store all promises that needs to be resolved 48 | * @type Array 49 | */ 50 | const promises = []; 51 | 52 | // Queue all promises to resolve 53 | for (const key in route.meta.resolve) { 54 | promises.push(route.meta.resolve[key].apply(this, Object.values(route.params))); 55 | } 56 | 57 | // And resolve them 58 | return Promise.all(promises); 59 | }; 60 | 61 | /** 62 | * Populate the resolved responses to the current resolve route keys 63 | * 64 | * @param {Component} vm - Vue component object to set the resolved data. 65 | * @param {Array} keys - Array of keys of the resolve object of the route. 66 | * @param {Array} data - Data for each key with matching indexes. 67 | */ 68 | const populate = (vm, keys, data) => { 69 | // Assign the data to the proper resolve key 70 | for (const index in keys) { 71 | const key = keys[index]; 72 | let value = data[index]; 73 | 74 | // If we have a dataProperty to eval, do it now 75 | if (userOptions.dataProperty) { 76 | value = value[userOptions.dataProperty]; 77 | } 78 | 79 | // Assign it to the component 80 | vm[key] = value; 81 | } 82 | } 83 | 84 | // Before entering each route, try to resolve the dependencies and only enter 85 | // if everything resolved successfully. 86 | router.beforeEach((to, from, next) => { 87 | // If nothing to resolve, continue normally 88 | if (!to.meta || !to.meta.resolve) { 89 | return next(); 90 | } 91 | 92 | // Resolve and move on 93 | resolveRoute(to) 94 | .then((responses) => { 95 | // Enter the component now, as we need the component instance to 96 | // be able to populate it with the resolve data. 97 | next(); 98 | 99 | // After mounting the component, populate it with the resolved data 100 | Vue.nextTick(() => { 101 | // We're gonna store the component here 102 | let component = null; 103 | 104 | for (const match of to.matched) { 105 | if (match.meta !== to.meta) { 106 | continue; 107 | } 108 | 109 | component = match.instances.default; 110 | 111 | // Populate the resolved component data 112 | populate(component, Object.keys(to.meta.resolve), responses); 113 | } 114 | 115 | // Run any `resolved` option on the component 116 | if (component.$options.resolved) { 117 | component.$options.resolved.bind(component)(); 118 | } 119 | }); 120 | }) 121 | .catch((responses) => { 122 | console.error(responses); 123 | });; 124 | }); 125 | 126 | // $resolve method as a mixin 127 | Vue.mixin({ 128 | methods: { 129 | /** 130 | * Re-resolve the dependencies for the current route 131 | */ 132 | $resolve() { 133 | if (!this.$route.meta.resolve) { 134 | return; 135 | } 136 | 137 | resolveRoute(this.$route) 138 | .then((responses) => { 139 | // Re-populate the data 140 | populate(this, Object.keys(this.$route.meta.resolve), responses); 141 | }) 142 | .catch((responses) => { 143 | console.error(responses); 144 | }); 145 | } 146 | } 147 | }); 148 | } 149 | }; -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const { mix } = require('laravel-mix'), 2 | path = require('path'); 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Mix Asset Management 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Mix provides a clean, fluent API for defining some Webpack build steps 10 | | for your Laravel application. By default, we are compiling the Sass 11 | | file for the application as well as bundling up all the JS files. 12 | | 13 | */ 14 | 15 | // Add custom path configuration to Webpack 16 | mix 17 | 18 | .webpackConfig({ 19 | resolve: { 20 | modules: [ 21 | path.resolve('./node_modules') 22 | ] 23 | } 24 | }); 25 | 26 | // And now the example 27 | mix 28 | .autoload({}) 29 | 30 | .copy('./example/index.html', 'public/index.html') 31 | 32 | .js('./example/index.js', 'public/js/app.js') 33 | 34 | // And browser-sync as dev server 35 | .browserSync({ 36 | proxy: false, 37 | server: { 38 | baseDir: './public' 39 | }, 40 | open: 'external', 41 | notify: false, 42 | ui: false 43 | }) 44 | 45 | .sourceMaps(); --------------------------------------------------------------------------------