Subscribe on changes!

Vue is not defined in Tampermonkey user script

avatar
Jan 1st 2022

Version

3.2.26

Reproduction link

yanxintang.github.io

Steps to reproduce

  1. Install tampermonkey extension for Chrome
  2. Open the reproduction link and click the link to install user script
  3. Open chrome devtool and refresh page then you will get the error that Vue is not defined

What is expected?

There is no error in devtool and the page shoud show the text 'bar'

What is actually happening?

The page is blank and there is an error in the devtool

avatar
Jan 1st 2022
(function() {
  'use strict';
    window.Vue = Vue // add this line
    Vue.createApp({
        data() {
            return {
                foo: 'bar'
            }
        },
        template: '{{ foo }}'
    }).mount('#app');
})();
avatar
Jan 1st 2022

Tampermonkey user script is running in the sandbox to avoid pollution the global scope. Apparently registry the Vue in global scope will break the peace.

This solution can work but can't work perfectly.

avatar
Jan 1st 2022

@YanxinTang OK, keep this opened.

avatar
Jan 4th 2022

The tampermonkey extension seems to inline the @require-ed script ahead of the user script, this produces the following total script:

The tampermonkey script (15k+ lines)
(function(that){((context, fapply, console) => {with (context) {(module => {"use strict";try {fapply(module, context, [,,context.CDATA,context.uneval,context.define,context.module,context.exports,context.GM,context.GM_info]);} catch (e) {if (e.message && e.stack) {console.error("ERROR: Execution of script 'vue-next-in-tampermonkey' failed! " + e.message);console.log(e.stack.replace(/(\\(eval at )?<anonymous>[: ]?)|([\s.]*at Object.tms_[\s\S.]*)/g, ""));} else {console.error(e);}}})(async function (context,fapply,CDATA,uneval,define,module,exports,GM,GM_info) {
var Vue = (function (exports) {
  'use strict';

 /* Vue source code, too large to post. See https://cdn.jsdelivr.net/npm/vue@3.2.26/dist/vue.global.js */

  return exports;

}({}));

// ==UserScript==
// @name         vue-next-in-tampermonkey
// @namespace    https://yanxintang.github.io/vue-next-in-tampermonkey/
// @version      0.1
// @author       tyx1703
// @match        https://yanxintang.github.io/vue-next-in-tampermonkey/
// @icon         https://www.google.com/s2/favicons?domain=0.1
// @grant        none
// @require      https://cdn.jsdelivr.net/npm/vue@3.2.26/dist/vue.global.js
// ==/UserScript==

(function() {
  'use strict';
  Vue.createApp({
    data() {
      return {
        foo: 'bar'
      }
    },
    template: '{{ foo }}'
  }).mount('#app');
})();
})}})(that.context, that.fapply, that.console);
//# sourceURL=moz-extension://c5be29cc-d3dc-f440-a58b-26cd80283619/userscripts/vue-next-in-tampermonkey.user.js?id=1843e8db-5ec7-49d7-b38d-cecf405ffe05
})((()=>{const k="__u__14329544.216160614",r=this[k];delete this[k];return r;})())

The stacktrace of the indicated error to starts at compileToFunction, which is trying to compile the following code:

const _Vue = Vue

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString } = _Vue

    return _toDisplayString(foo)
  }
}

Printing Vue just ahead of the function compilation shows that it actually is the Vue object. So for some reason the dynamically generated function does not get to use the context of the function creator.

avatar
Jan 4th 2022

Looking at the MDN docs for the Function constructor shows that code compiled this way can only ever reference global variables. This means that in order to resolve this issue one of these should happen:

  1. Vue is globally available (on the target document, not the script's context)
  2. No runtime compilation of vue templates is used (only using render functions)
  3. The compiled function is passed the script's global context (or Vue) in some manner.

This could also be achieved by wrapping the generated code in one more function call:


function compileToFunction(template, options) {
  /* Vue's code to compile template to a javascript string */
  const code = `
    const _Vue = Vue

    return function render(_ctx, _cache) {
      with (_ctx) {
        const { toDisplayString: _toDisplayString } = _Vue

        return _toDisplayString(foo)
      }
    }
  `;

  const factory = `return function renderFactory(Vue) { ${code} }`;

  // Compile the factory function, obtain it with `()`, then use it with `(Vue)`.
  // This passes the locally known global Vue variable into the render function.
  const render = new Function(factory)()(Vue);
  render._rc = true;
  return (compileCache[key] = render);
}
avatar
Jan 5th 2022

Yes, I have created a pull request for this problem. https://github.com/vuejs/vue-next/pull/5197

avatar
Jul 8th 2023
(function() {
  'use strict';
    unsafeWindow.Vue = Vue // replace window with unsafeWindow
    Vue.createApp({
        data() {
            return {
                foo: 'bar'
            }
        },
        template: '{{ foo }}'
    }).mount('#app');
})();

replacing window with unsafeWindow seems work.