From e742bcf0d187f8283ff171ec9760431759b55910 Mon Sep 17 00:00:00 2001 From: Sergii Stotskyi Date: Fri, 22 Jan 2021 15:47:50 +0200 Subject: [PATCH] refactor(vue): adds support for vue 3 (#444) Fixes #396 BREAKING CHANGE: refactor to use Vue 3 what introduces a bunch of breaking changes: * `Ability` instance is not a required plugin parameter. Previously, we could decide whether to pass ability as plugin parameter or as root component option. Now, the only way is to pass it in plugin: **Before** ```js import { abilitiesPlugin } from '@casl/vue'; import Vue from 'vue'; import { ability } from './services/AppAbility'; Vue.use(abilitiesPlugin); new Vue({ ability }).$mount('#app') ``` **After** ```js import { abilitiesPlugin } from '@casl/vue'; import { createApp } from 'vue'; import { ability } from './services/AppAbility'; createApp() .use(abilitiesPlugin, ability) .mount('#app'); ``` * `abilitiesPlugin` no more define global `$ability` and `$can` properties, instead a recommended way to get `AppAbility` instance is by injecting it through [provide/inject API](https://v3.vuejs.org/guide/component-provide-inject.html). To get previous behavior, pass `useGlobalProperties: true` option: **Before** ```js import { abilitiesPlugin } from '@casl/vue'; import Vue from 'vue'; import { ability } from './services/AppAbility'; Vue.use(abilitiesPlugin); const root = new Vue({ ability }).$mount('#app') console.log(root.$ability) ``` **After** Recommended way: ```js import { abilitiesPlugin, ABILITY_TOKEN } from '@casl/vue'; import { createApp } from 'vue'; import { ability } from './services/AppAbility'; const App = { name: 'App', inject: { $ability: { from: ABILITY_TOKEN } } }; const root = createApp(App) .use(abilitiesPlugin, ability, { useGlobalProperties: true }) .mount('#app'); console.log(root.$ability) ``` Backward compatible way: ```js import { abilitiesPlugin } from '@casl/vue'; import { createApp } from 'vue'; import { ability } from './services/AppAbility'; const root = createApp() .use(abilitiesPlugin, ability, { useGlobalProperties: true }) .mount('#app'); console.log(root.$ability) ``` * `AllCanProps` type was renamed to `CanProps` * `@casl/vue` no more augment vue types, so if you decide to use global properties, you will need to augment types by yourself **Before** @casl/vue augments type of `$ability` to `AnyAbility` and `$can` to `typeof $ability['can']` **After** create a separate file `src/ability-shim.d.ts` with the next content: ```ts import { AppAbility } from './AppAbility' declare module 'vue' { interface ComponentCustomProperties { $ability: AppAbility; $can(this: this, ...args: Parameters): boolean; } } ``` --- .eslintrc | 8 + babel.config.js | 2 +- packages/casl-mongoose/package.json | 4 +- packages/casl-vue/README.md | 301 +- packages/casl-vue/index.d.ts | 1 - packages/casl-vue/package.json | 6 +- packages/casl-vue/patch.d.ts | 15 - packages/casl-vue/spec/can.spec.js | 147 +- packages/casl-vue/spec/hooks.spec.js | 49 + packages/casl-vue/spec/plugin.spec.js | 127 +- packages/casl-vue/src/component/can.ts | 86 +- packages/casl-vue/src/index.ts | 7 +- packages/casl-vue/src/plugin.ts | 65 +- packages/casl-vue/src/reactiveAbility.ts | 21 + packages/casl-vue/src/types.ts | 4 - packages/casl-vue/src/useAbility.ts | 19 + packages/casl-vue/tsconfig.build.json | 5 +- packages/casl-vue/tsconfig.json | 5 +- pnpm-lock.yaml | 3347 +++++++++++----------- 19 files changed, 2180 insertions(+), 2039 deletions(-) delete mode 100644 packages/casl-vue/index.d.ts delete mode 100644 packages/casl-vue/patch.d.ts create mode 100644 packages/casl-vue/spec/hooks.spec.js create mode 100644 packages/casl-vue/src/reactiveAbility.ts delete mode 100644 packages/casl-vue/src/types.ts create mode 100644 packages/casl-vue/src/useAbility.ts diff --git a/.eslintrc b/.eslintrc index 6fba2b2e7..a08c1397f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -82,6 +82,14 @@ }], "import/no-extraneous-dependencies": "off" } + }, + { + "files": [ + "packages/casl-vue/spec/**/*.{js,ts}" + ], + "env": { + "browser": true + } } ] } diff --git a/babel.config.js b/babel.config.js index cff7202aa..2d2f9dbe3 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,7 +2,7 @@ const CONFIG = { default: { plugins: [ ['@babel/plugin-transform-typescript', { - allowDeclareFields: true + allowDeclareFields: false }], ['@babel/plugin-proposal-class-properties', { loose: true diff --git a/packages/casl-mongoose/package.json b/packages/casl-mongoose/package.json index b8737ed9d..2eda01dce 100644 --- a/packages/casl-mongoose/package.json +++ b/packages/casl-mongoose/package.json @@ -40,7 +40,7 @@ "license": "MIT", "peerDependencies": { "@casl/ability": "^3.0.0 || ^4.0.0 || ^5.1.0", - "mongoose": "^4.0.0 || ^5.0.0" + "mongoose": "^4.0.0 || <= 5.10.0" }, "devDependencies": { "@babel/core": "^7.8.4", @@ -62,7 +62,7 @@ "eslint-config-airbnb-typescript": "^12.0.0", "eslint-plugin-import": "^2.20.2", "jest": "^26.0.0", - "mongoose": "^5.0.14", + "mongoose": "~5.10.0", "rollup": "^2.10.9", "rollup-plugin-sourcemaps": "^0.6.2", "rollup-plugin-terser": "^7.0.0", diff --git a/packages/casl-vue/README.md b/packages/casl-vue/README.md index bb83e3103..6b708d813 100644 --- a/packages/casl-vue/README.md +++ b/packages/casl-vue/README.md @@ -4,10 +4,22 @@ [![](https://img.shields.io/npm/dm/%40casl%2Fvue.svg)](https://www.npmjs.com/package/%40casl%2Fvue) [![CASL Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/stalniy-casl/casl) -This package allows to integrate `@casl/ability` with [Vue] application. So, you can show or hide UI elements based on user ability to see them. This package provides a Vue plugin which defines `$ability` object and `$can` method for all components. Also package provides functional `Can` component (not included in the plugin), both allow to hide or show UI elements based on the user ability to see them. +This package allows to integrate `@casl/ability` with [Vue 3] application. So, you can show or hide UI elements based on user ability to see them. ## Installation +**For Vue 2.x**: + +```sh +npm install @casl/vue@1.2.1 @casl/ability +# or +yarn add @casl/vue@1.2.1 @casl/ability +# or +pnpm add @casl/vue@1.2.1 @casl/ability +``` + +**For Vue 3.x**: + ```sh npm install @casl/vue @casl/ability # or @@ -18,65 +30,134 @@ pnpm add @casl/vue @casl/ability ## Getting started -If you don't plan to use multiple `Ability` instances across your application (99.9% likelihood that you don't), you can pass `Ability` instance as a 2nd argument to `Vue.use`: +This package provides a Vue plugin which defines `$ability` object and `$can` method for all components, in the same way as it was for Vue 2.x. Additionally, this package provides `useAbility` and `provideAbility` hooks that can be used with new [Vue Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html). -```js @{data-filename="main.js"} -import Vue from 'vue'; +### Vue plugin if you need some backward compatibility + +`abilitiesPlugin` is left for backward compatibility with Vue 2.x and provides global `$ability` and `$can` properties. However,`Ability` instance is now a mandatory argument for plugin: + +```js +import { createApp } from 'vue'; import { abilitiesPlugin } from '@casl/vue'; import ability from './services/ability'; -Vue.use(abilitiesPlugin, ability); +createApp() + .use(abilitiesPlugin, ability) + .mount('#app'); ``` -but if you one from that 0.1%, you need to pass it in `Vue` constructor: +`Can` component is not registered by the plugin, so we can decide whether we want to use component or `v-if` + `$can` method. Also, this helps tree shaking to remove it if we decide to not use it. So, to register component globally just use global API ([read more](#can-component)): -```js @{data-filename="main.js"} -import Vue from 'vue'; -import { abilitiesPlugin } from '@casl/vue'; -import ability from './services/ability'; +```js +import { Can } from '@casl/vue'; -Vue.use(abilitiesPlugin); +createApp() + .use(abilitiesPlugin, ability) + .component(Can.name, Can) + .mount('#app'); +``` + +Later, we can use either `$ability` or `$can` method in any component: -new Vue({ - el: '#app', - ability -}) +```vue + ``` -The difference is that the 1st approach defines `Ability` instance on `Vue.prototype` and 2nd one passes ability from parent to child in component tree. +`globalProperties` is the same concept as a global variables which make life more complicated because any component has access to them (i.e., implicit dependency) and we need to ensure they don't introduce name collisions by prefixing them. So, instead of exposing `$ability` and `$can` as globals, we can use [provide/inject API](https://v3.vuejs.org/guide/component-provide-inject.html) to get access to `$ability`: -> The 2nd approach potentially may slowdown components creation but you will not notice this ;) +```js +createApp() + .use(abilitiesPlugin, ability, { + defineGlobals: false // disable globalProperties pollution + }) + .mount('#app'); +``` -The plugin doesn't register `Can` component, so you can decide whether to use it or not. In most cases, `$can` function is enough and it's more lightweight than `Can` component. +To inject an `Ability` instance, we can use `ABILITY_TOKEN`: -To use `Can` functional component, you need to import it in a particular component or register it globally: +```vue + -```js -import Vue from 'vue'; -import { Can } from '@casl/vue'; + ``` -> See [CASL guide](https://casl.js.org/v5/en/guide/intro) to learn how to define `Ability` instance. +This is a bit more complicated but allows us to be explicit. This works especially good with new [Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html): -## Check permissions in templates +```vue + -To check permissions, you can use `$can` method in any component, it accepts the same arguments as `Ability`'s `can`: + +``` + +### provideAbility hook + +Very rarely, we may need to provide a different `Ability` instance for a sub-tree of components, and to do this we can use `provideAbility` hook: + +```vue + + ``` -## Can component +> See [CASL guide](https://casl.js.org/v5/en/guide/intro) to learn how to define `Ability` instance. -There is an alternative way you can check your permissions in the app by using the `Can` component. Instead of using `v-if="$can(...)"`, we can do this: -```html +### Can component + +There is an alternative way we can check permissions in the app, by using `Can` component: + +```vue