Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Vue3 Provide/Inject Plugin #518

Merged
merged 4 commits into from
Mar 11, 2022
Merged

Conversation

c-fitzmaurice
Copy link
Contributor

This PR adds a new plugin for Vue3 using Ziggy via the Provide/Inject Composition API. Using the advanced Vue set up, I ran into my problem when trying to use route() in setup(). I saw a possible solution in #454 (comment) but it didn't feel like a super maintainable solution, also mixins are not recommended in Vue3 anymore.

Note: I'm not familiar with microbundles, so if anything needs to be edited in package.json, please let me know.

For this to work, you have to update your mix alias like below:

mix.alias({
    ziggy: path.resolve('vendor/tightenco/ziggy/dist/vue3'), 
});

and the import of ZiggyVue3:

import { createApp } from 'vue';
import { ZiggyVue3 } from 'ziggy';
import { Ziggy } from './ziggy';
import App from './App';

createApp(App).use(ZiggyVue3, Ziggy);

The route() helper will still be registered globally and you gain the below functionality:

<script>
import { inject } from 'vue';

export default {
    setup() {
        const route = inject('route');

        const navigation = [{
            name: 'Profile',
            href: route('profile'),
            current: route().current('profile'),
        }, {
            name: 'Settings',
            href: route('settings'),
            current: route().current('settings'),
        }, {
         ...
        }];

        return {
            navigation,
        };
    },
};
</script>

src/js/vue3.js Outdated
install: (app, options) => {
const _route = (name, params, absolute, config = options) => route(name, params, absolute, options);

app.config.globalProperties.route = _route;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly does this line do? Adding it to globalProperties specifically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this line, a user would have to have to inject route() into every component, even if they don't need it in the setup() function. This allows route() to be used in templates globally.

The below would have to be in every Vue component that wants to use route().

<script>
import { inject } from 'vue';
export default {
    setup() {
        const route = inject('route');
        return { route };
    },
};
</script>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serving as the mixin to make the function globally available to Vue.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if the globalProperties thing works exactly the same as mixins? Can you still call this.route() inside components using the Options API?

Copy link
Contributor

@Tofandel Tofandel Mar 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bakerkretzmar What do you mean by "inside components" do you mean in the template or in the script? Because if it's in the script the composition API doesn't have a this that's why traditional mixins don't work in the composition API and we need a useRoute method, the reason being mixins are anti patterns because they are globally on the vue instance and there is a lot of conflicts between mixins of different libraries because none uses a unique name, but for the template route remains accessible without declaring anything just like a mixin

And if youre just asking about this.route` within template, you're pretty much not allowed to use this in a template, eslint will remove them, but if you don't have eslint then it doesn't matter because vue-template-compiler compiles them like they don't exist

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I just meant using this.route() inside the script part of a component using the Options API.

@bakerkretzmar
Copy link
Collaborator

bakerkretzmar commented Feb 22, 2022

Thanks for the PR! Making inject work out of the box like this would be awesome. Would it be possible to accomplish this using our existing Vue plugin? Could we just tweak it a bit and add app.provide('route', route)?

@c-fitzmaurice
Copy link
Contributor Author

@bakerkretzmar I can def give it a shot to add it to the existing Vue plugin, but on the Vue 2 docs the provide/inject API is only supported in v2.2.0+. If you're not worried about that requirement, I'll circle back later today!

@c-fitzmaurice
Copy link
Contributor Author

@bakerkretzmar After spinning up a Vue2 Inertiajs demo, I don't think it will be possible with one plugin. I believe that the Vue2 provide/inject is only from parent to child component and not possible globally. I could be wrong if you have better Vue2 chops than I do and want to give it a shot, sorry.

@Tofandel
Copy link
Contributor

I was about to make a similar PR so I'll add my 2 grains of salt, it's definitely possible to support it in a single plugin

The prefered method is to export a "useRoute" method which will return the route method created when installing the plugin

@Tofandel
Copy link
Contributor

Tofandel commented Feb 23, 2022

This is pretty much the PR I was gonna make

import route from './index.js';

let installedRoute = null;

export const ZiggyVue = {
  install: (v, options, property = 'route') => {
    installedRoute = (name, params, absolute, config = options) => route(name, params, absolute, config);
    if (property) {
      v.config && (v.config.globalProperties[property] = installedRoute);
      v.mixin && v.mixin({
        methods: {
          [property]: installedRoute,
        },
      });
    }
  },
};


export const useRoute = () => installedRoute;

The inject provide is a good addition to support the possibility of multiple vue instances in the entry point with multiple installation of the plugin but then makes the plugin dependant on vue version and it would indeed need 2 separate plugins for vue2 and vue3

Usage in a setup

import {useRoute} from "ziggy-vue";

const route = useRoute();

I updated #512 with this

@bakerkretzmar
Copy link
Collaborator

I think we can probably get this working without a separate Vue 3 plugin, Vue has a version property we can check. Something like import { version } from 'vue' and then if we're on Vue 3, we call app.provide().

I don't want to split it out into two different plugins if possible.

@Tofandel
Copy link
Contributor

Tofandel commented Feb 28, 2022

@bakerkretzmar Well, I have a version not using inject provide that does that, I'd like to use inject provide as well, the problem is with the ìmport { inject } from 'vue'; because we can't have dynamic imports and the inject doesn't exist in vue 2 it woud likely throw an error, it would also not work in the non npm build

@bakerkretzmar
Copy link
Collaborator

Not import { inject } from 'vue', import { version } from 'vue'. version exists in Vue 2 and 3.

@Tofandel
Copy link
Contributor

Tofandel commented Mar 8, 2022

I don't think you understood the issue, the problem is with microbundle --external none then if we import { version } from vue then it won't matter the version because it will bundle vue and use the vue installed in ziggy, which is a problem. This is what I'm saying, anything importing something from vue is a problem with this configuration

@c-fitzmaurice
Copy link
Contributor Author

c-fitzmaurice commented Mar 11, 2022

Okay, so I have read up on this a little more and just pushed an update to this PR.

@Tofandel I tested your solution and it works, but it may present some unneeded complexity with the options of changing the route name and the ability to turn off the global mixin in favor of an import with useRoute(). A user can just not install the plugin into the Vue instance and import route directly with import route from '../../vendor/tightenco/ziggy/dist'; if they want to.

In the new implementation, I just pushed I also decided to remove the below line since the new version left the Vue 2 implementation unaffected.

app.config.globalProperties.route = _route;

The new version allows the Vue 2 implementation to work without any breaking changes and provides Vue 3 with Provide/Inject, also without changing the installation steps. Mixins are available in Vue 3 but there are many places in the docs saying they are no longer recommended. There are many blog posts out there listing the reasons why. I tested all the below implementations and can confirm they work. So this version provides a clear break in the plugin to align with Vue best practices, Vue 2 - Mixin is okay, Vue 3 - Provide/Inject.

Note: There are several ways to check if provide is undefined or parsing out the Vue version. If you prefer another syntax just let me know. Also, version > 2 was an attempt to potentially future-proof it a little assuming the composition API is here to stay.

Vue 2 - Unaffected

<template>
    <a :href="route('home')">route</a>
    <a :href="url">url</a>
</template>

<script>
export default {
    data() {
        return {
            url: this.route('home'),
        }
    },
};
</script>

Vue 3 - Options API

<template>
    <a :href="route('home')">route</a>
    <a :href="url">url</a>
</template>

<script>
export default {
    inject: ['route'],
    data() {
        return {
          url: this.route('home'),
        }
    }
}
</script>

Vue 3 - Composition API

<template>
    <a :href="route('home')">route</a>
    <a :href="url">url</a>
</template>

<script setup>
import { inject } from 'vue';
const route = inject('route');

const url = route('home');
</script>

Copy link
Collaborator

@bakerkretzmar bakerkretzmar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@c-fitzmaurice thanks a lot for digging into this further. I had another look at it last week too and landed on something very similar but forgot to come back and comment 🤦🏻

Vue 2: https://codesandbox.io/s/ziggy-vue-2-n3x2e0
Vue 3: https://codesandbox.io/s/ziggy-vue-3-xnz66z

I left a couple comments but after they're resolved I think this is ready to go. I'm on board with removing the mixin eventually but that's a significant breaking change so it'll have to wait until v2.

@c-fitzmaurice
Copy link
Contributor Author

@bakerkretzmar Updated the PR with the code from the below as discussed.

https://stackblitz.com/edit/vitejs-vite-zuznmu

@bakerkretzmar bakerkretzmar merged commit 5661ec3 into tighten:main Mar 11, 2022
@bakerkretzmar
Copy link
Collaborator

@c-fitzmaurice thanks a lot!

@Tofandel
Copy link
Contributor

Tofandel commented Mar 11, 2022

The check should not have been on the version but just whether or not provide is available, as the composition api is available in vue 2 with an official package

@bakerkretzmar
Copy link
Collaborator

bakerkretzmar commented Mar 14, 2022

Installing @vue/composition-api doesn't seem to make provide available to the Ziggy plugin, v.provide is still undefined: https://codesandbox.io/s/ziggy-vue-2-composition-1kty9l?file=/src/plugin.js

@Tofandel
Copy link
Contributor

Interesting find, then I guess users of vue2 composition API will have to provide it manually in their root component

prestonholt added a commit to prestonholt/jetstream-inertia-ssr-ziggy that referenced this pull request Jun 14, 2022
Ziggy Vue plugin allows `route()` to be injected into `setup()` function in components[1][2], which is needed for Inertia SSR since the @routes blade directive isn't available.

[1]: tighten/ziggy#518
[2]: tighten/ziggy#564 (comment)
taylorotwell added a commit to laravel/jetstream that referenced this pull request Jun 17, 2022
* Use Ziggy Vue plugin instead of route mixin

Ziggy Vue plugin allows `route()` to be injected into `setup()` function in components[1][2], which is needed for Inertia SSR since the @routes blade directive isn't available.

[1]: tighten/ziggy#518
[2]: tighten/ziggy#564 (comment)

* Fix quotations

* Update app.js

Co-authored-by: Taylor Otwell <[email protected]>
Tofandel added a commit to Tofandel/ziggy that referenced this pull request Jan 23, 2023
@psr-cookies
Copy link

So this version provides a clear break in the plugin to align with Vue best practices, Vue 2 - Mixin is okay, Vue 3 - Provide/Inject.

The ideal would be to use composables with the Vue Composition API

What is a "Composable"?

In the context of Vue applications, a "composable" is a function that leverages Vue's Composition API to encapsulate and reuse stateful logic.

Provide / Inject serve to pass information from a parent component to another child component, without going through the parent component.

With the "Composition API" option, using Provide/Inject is not best practice in Vue 3.

growno1127 added a commit to growno1127/jetstream that referenced this pull request Mar 15, 2024
* Use Ziggy Vue plugin instead of route mixin

Ziggy Vue plugin allows `route()` to be injected into `setup()` function in components[1][2], which is needed for Inertia SSR since the @routes blade directive isn't available.

[1]: tighten/ziggy#518
[2]: tighten/ziggy#564 (comment)

* Fix quotations

* Update app.js

Co-authored-by: Taylor Otwell <[email protected]>
panich76 added a commit to panich76/jetstream that referenced this pull request Mar 5, 2025
* Use Ziggy Vue plugin instead of route mixin

Ziggy Vue plugin allows `route()` to be injected into `setup()` function in components[1][2], which is needed for Inertia SSR since the @routes blade directive isn't available.

[1]: tighten/ziggy#518
[2]: tighten/ziggy#564 (comment)

* Fix quotations

* Update app.js

Co-authored-by: Taylor Otwell <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants