1

My target is to change / extend the template of a production vue.js app with a greasemonkey script in a third-party website.
(so I have no access to the site / app sources)

It is possible to get access to the Vue instance in the browser console with

Array.from(document.querySelectorAll('*')).find(e => e.__vue__).__vue__

(based on this answer)

but I did not get this to work from gm:

// ==UserScript==
// @name     vue.js app instance test
// @version  0.1.0
// @grant    none
// @namespace   https://github.com/s-light
// @match https://s8zf8m.codesandbox.io/*
// ==/UserScript==

window.addEventListener('load', () => {
    // console.info('All resources finished loading.');
    console.info('vue.js app instance test');
    window.setTimeout(() => {
        get_vue_app_instance();
    }, 2000);
});


function get_vue_app_instance() {
    console.group('get_vue_app_instance');
    let app = null;
    let app_el = null;

    // based on
    // https://stackoverflow.com/a/69466883/574981

    try {
        // const app = Array.from(document.querySelectorAll('*')).find(e => e.__vue__).__vue__;
        app_el = Array.from(document.querySelectorAll('*')).find(e => e.__vue__);
        console.log('Array.from(document.querySelectorAll(\'*\')).find(e => e.__vue__)', app_el);

        if (!app_el) {
            app_el = document.querySelector('[app-data]');
            console.log('document.querySelector(\'[app-data]\') app_el:', app_el);
        }

        if (!app_el) {
            app_el = document.querySelector('#app');
            console.log('document.querySelector(\'#app\') app_el:', app_el);
        }
    } catch (e) {
        console.warn(e);
    }

    try {
        console.log('app_el.__vue__', app_el.__vue__);
        console.log('typeof(app_el.__vue__)', typeof(app_el.__vue__));
        app = app_el.__vue__;
    } catch (e) {
        console.warn(e);
    }

    if (!app) {
        console.warn('app instance not found.', app);
    } else {
        console.info('app instance:', app);
    }
    console.groupEnd();
    return app;
}

on this simple Vue app

which results in enter image description here

In the last two lines, I just tried in the console - and it results in the correct div. To me, it seems gm has no access / does not see the __vue__ property..

And also I am unsure if it is possible at all to change the template / recompile it in the running app. And I did not find any hint on how to access the template at all.

So is it possible to edit / modify the template with greasemonkey?

David Buck
  • 3,752
  • 35
  • 31
  • 35
  • 1
    Consider explaining your case in details. This is very specific to it. `__vue__` gets app instance. If these are global components and you can access them before the app mounted, comp `template` can be patched, but most times this is impossible. If it's third-party website, it should preferably be specified, as an answer would require to check the way it works. Otherwise you may try to use Vue devtools protocol to hook into components. Since it's not available in production, this would require something like this extension does, https://github.com/hzmming/vue-force-dev/blob/master/hack.js – Estus Flask Sep 11 '22 at 09:28
  • This is Quasar and not just Vue app, it may have its quirks based on internal structure. It's prod app and can't use devtools protocol. There's no straightforward way to do this because the app is bundled, and the comp is private module deep inside of it, so the only way to get comp reference and modify `template` is to hack into the framework or one of public components - RouterView in this case. Even if possible, this would be bulky and fragile implementation. I guess the recommended way here is to not tie to Vue implementation and modify rendered DOM with the help from MutationObserver, etc – Estus Flask Sep 11 '22 at 12:11
  • thanks @EstusFlask - i changed the example to a very simple vue2 one. (i can not share the target app..) so do you mean it is not possible to intercept the app mounted event if i only have the instance? or only very tricky? ;-) – Stefan Krüger s-light Sep 12 '22 at 04:38
  • and i am with you - it is easier to manipulate the DOM in other ways - and a good idea to learn about the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) things ;-) – Stefan Krüger s-light Sep 12 '22 at 04:39
  • It's possible in theory but too far from being practical. It totally depends on your specific case. You need to find a place where you can hack into. Changing a template at runtime presumes that you need to change comp's render fn, and you'll have more problems with this in V3 app because it's usually 100% modular, and you need to get `h` from bundled module (it should come from the same Vue copy as the app). In this example it would be very easy because there are no nested comps. `app_el = Array.from(...); app_el.$options.render = (h) => h('p', {}, 'Bye'); app_el.$forceUpdate()` – Estus Flask Sep 12 '22 at 04:54
  • It's hard to impossible to intercept mounted because almost always it happens immediately after app instance is created, i.e. at the exact time when app script is loaded. You'd need to postpone app `mount()` or hook into the lifecycle by patching the framework, it's not straightforward in V2, even less in V3, probably impossible from GM perspective in bundled app. The mentioned hack with `__vue__` relies on that an app has already been mounted to DOM, it won't appear before that. – Estus Flask Sep 12 '22 at 05:01
  • Thanks @EstusFlask for your detailed explanation!! that is really clear and understandable. would you mind to post it as answer? – Stefan Krüger s-light Sep 13 '22 at 05:43

1 Answers1

0

__vue__ gets app instance. If these are global components and you can access them before the app mounted, component template can be patched, but most times this is impossible.

Vue devtools protocol can be used to hook into components. It's not available in production, this would require something like vue-force-dev extension does, this method may not work in some cases because it relies on the internals of Vue apps.

There's no straightforward way to do for bundled applications. A component is private module deep inside of it, so the only way to get component reference and modify a template is to hack into the framework or one of global components, e.g. RouterView in case of router-based application.

A good case to patch an application from the outside (common for legacy Vue 2 applications with Vue CDN):

Vue.component('global-component', GlobalComponent)

A bad case (common for modern Vue 3 applications):

<script setup>
import LocalComponent from '.../LocalComponent .vue';
...

For Vue 2 top-level component like in the question this is the most easy:

app_el = Array.from(document.querySelectorAll('*')).find(e => e.__vue__); 
app_el.$options.render = (h) => h('p', {}, 'Bye');
app_el.$forceUpdate()

It's harder in Vue 3 because this requires to import h function from bundled Vue copy.

In case this cannot be easily done, the recommended way is to not rely on Vue implementation but treat the application as black box and modify rendered DOM per need basis, through MutationObserver, etc

Estus Flask
  • 206,104
  • 70
  • 425
  • 565