Search code examples
vue.jsgreasemonkey-4

How can I use greasemonkey to change vue.js app model / template?


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?


Solution

  • __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