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

How to use Swup with VueJs #208

Closed
lionettiluca opened this issue Oct 1, 2019 · 16 comments
Closed

How to use Swup with VueJs #208

lionettiluca opened this issue Oct 1, 2019 · 16 comments

Comments

@lionettiluca
Copy link

Hi guys,

which is the best way to use Swup page transition with VueJs components?

I need to use Swup because the project is based on CMS and I need to manage dynamic routes. So, I tried to use the Vue method $destroy and re-mount the same instance but I've a common navbar components (the same for all pages) that after first page transition stop to be reactive.

Any ideas?

@lionettiluca lionettiluca changed the title VueJs usage How to use Swup with VueJs Oct 1, 2019
@AugustMiller
Copy link

Hey! Cool use case—I bet we can sort something out.

Do you mind posting a stripped-down sample of your HTML, and your current Swup config?

It might be worth taking a look at how the Gia plugin handles initializing components—basically, it surveys the freshly-injected HTML in each of the containers you set up with Swup, and mounts any matching g-component instances.

I have pretty limited experience with Vue, but it might make sense to use a similar approach, whereby on each contentReplaced event, you iterate over the containers and run new Vue({ el: container }); in each one (assuming the Vue constructor is aware of the components that will appear inside it).

This actually sounds like it'd make a great plugin—let me know more about your current implementation, and we can hack around a bit! ✌️

@lionettiluca
Copy link
Author

Of course @AugustMiller!
I think there is a way to implement Vue with GiaPlugin but I don't know how.

So, this is my HTML:

<main id="app">
    <!-- navbar vue component -->
    <navbar>
       ...
    </navbar>
    <!-- / navbar vue component -->

    <!-- page content -->
    <!-- with many components -->
     ...
</main>

And this is my JS configuration file:

window.Vue = require('vue');

import Swup from 'swup';
import SwupHeadPlugin from '@swup/head-plugin';
import SwupDebugPlugin from '@swup/debug-plugin';

var vm;

/** ----------------------------------------------------------------------------
 * VUE
 * Components registration
 * -------------------------------------------------------------------------- */
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default));

/** ----------------------------------------------------------------------------
 * Init
 * -------------------------------------------------------------------------- */
function init() {
    vm = new Vue({
        el: '#app',
        mounted() {
            swup.on('willReplaceContent', unload);
            swup.on('contentReplaced', init);
        }
    });
}


/** ----------------------------------------------------------------------------
 * Unload
 * -------------------------------------------------------------------------- */
function unload() {
    vm.$destroy();
}

/** ----------------------------------------------------------------------------
 * Start
 * -------------------------------------------------------------------------- */
const swup = new Swup({
    containers: ["#app"],
    plugins: [
        new SwupHeadPlugin(),
        new SwupDebugPlugin()
    ]
});

init();

I've tried to $destroy and re-mount the Vue instance but it doesn't work.

@AugustMiller
Copy link

I don't think this is a final answer, but have you tried attaching the listeners outside your Vue component's lifecycle?

Maybe: create the Swup instance, and immediately attach your willReplaceContent and contentReplaced listeners—then (in unload), guard the call to vm.$destroy() (unless it's safe to call it again even when it might have already been destroyed).

I have a feeling you might be dealing with the init/unload callbacks being attached multiple times, or (potentially) in the wrong order…

Just to be clear, re: the Gia Plugin—it won't be required for your use case, but I thought they way Georgy implemented it was pretty slick. Your example isn't doing doing anything fundamentally different! Just noting that the plugin has access to containers defined in the Swup config, and is able to automatically create Gia components when you alter the elements Swup is managing (rather than, say, creating a second vm and manually initializing both of them. 😉

@AugustMiller
Copy link

AugustMiller commented Oct 2, 2019

Also, I wonder if (based on this explanation of the mounted method) whether or not the original #app element even exists after the initial mounting, or if it's "replaced" with the Vue-managed node? 😬

Do you mind trying to mount your root vm instance lower than the container Swap is managing?

Something like:

<div class="#swup">
  <div class="#app">
    <!-- Other components? -->
  </div>
</div>

…and then updating your Soup config to use containers: ['#swup'].

@lionettiluca
Copy link
Author

Thanks @AugustMiller for your replies!

Today I've tried to set my config file like this:

window.Vue = require('vue');

import Swup from 'swup';
import SwupHeadPlugin from '@swup/head-plugin';

/** ----------------------------------------------------------------------------
 * VUE
 * Components registration
 * -------------------------------------------------------------------------- */
const files = require.context('./', true, /\.vue$/i);
files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default));

/** ----------------------------------------------------------------------------
 * Init
 * -------------------------------------------------------------------------- */
function init() {
    swup.off();

    // VUE
    let app = new Vue({
        el: '#app',
        mounted() {
            // Set Swup event
            swup.on('willReplaceContent', () => {
                this.$destroy();
            });
        },
        beforeDestroy() {
            // destroy all instance of other packages
        }
    });

    // ...

    Vue.nextTick(() => {
        swup.on('contentReplaced', init);
    });
}

/** ----------------------------------------------------------------------------
 * Inizialize Swup
 * -------------------------------------------------------------------------- */
const swup = new Swup({
    cache: false,
    containers: ["#app"],
    plugins: [
        new SwupHeadPlugin()
    ]
});

init();

Now I'm testing if there are some bugs but It seem to work 😁

@andrispraulitis
Copy link

I believe with this method plugins with use of this.swup.on() does not work. Or I'm doing something wrong?

@andrispraulitis
Copy link

@lklion have you had any luck with fixing using with Vue?

@lionettiluca
Copy link
Author

Hi @andrispraulitis, in my case the solution I've posted above works fine.
Can you post your code here?

@andrispraulitis
Copy link

Hi @lklion yes solution works fine but none of the plugins works because swup.off() removes all handlers for all events. Do plugins work for you?

@lionettiluca
Copy link
Author

Hi @andrispraulitis, you are right, this snippet has some problems with HeadPlugin.
I’ll try to find a solution, if I find something I will post it

@lionettiluca
Copy link
Author

Hi @andrispraulitis, I find a quick solution.
This solutions works fine for me. It has the only limit with style in components because, with the SwupHeadPlugin, styles in head will not be consider after content replacing.

function init() {
    swup.off();

    swup.use(new SwupHeadPlugin());

    let app = new Vue({
        el: '#app',
        mounted() {
            swup.on('contentReplaced', init);

            swup.on('willReplaceContent', () => {
                this.$destroy();
            });
        }
    });
}

let swup = new Swup({
    cache: false,
    containers: ["#app"]
});

init();

@andrispraulitis
Copy link

Hi @lklion I'll have a look at this over weekend. Re-initiating plugins was the only fix I could think of just didn't know how to do it.

@andrispraulitis
Copy link

andrispraulitis commented Jan 31, 2020

Hi @lklion, I kept your original solution and simply moved swup plugins from swup config to init function with swup.use(pluginName) and seems to work now. Yes I understand style/script files will not get loaded with head plugin if you simply replace the tag, that is a separate issue but there is a Scripts Plugin so can start from there.

Any reason why you moved swup contentReplaced from Vue nextThick to mounted?

@andrispraulitis
Copy link

Also I would like to mention that plugin unmount is never called. Now that init function loads plugins those needs to be unloaded at some point.

@lionettiluca
Copy link
Author

Hi @andrispraulitis, I've moved the events declarations from nextTick to mounted because it's just an event declaration and you don't need to work on DOM.
For plugin you're right, I've added the unuse method on contentReplaced event.
I've moved also the swup.off() on this.

Mounted now:

mounted() {
    swup.on('contentReplaced', () => {
        swup.unuse('HeadPlugin');
        swup.off();

        init();
    });

    swup.on('willReplaceContent', () => {
        this.$destroy();
    });
}

@andrispraulitis
Copy link

Hi @lklion, Thanks seems to be working now.

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

No branches or pull requests

3 participants