Vue is not defined in Tampermonkey user script
Version
3.2.26
Reproduction link
Steps to reproduce
- Install tampermonkey extension for Chrome
- Open the reproduction link and click the link to install user script
- 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
(function() {
'use strict';
window.Vue = Vue // add this line
Vue.createApp({
data() {
return {
foo: 'bar'
}
},
template: '{{ foo }}'
}).mount('#app');
})();
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.
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.
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:
- Vue is globally available (on the target document, not the script's context)
- No runtime compilation of vue templates is used (only using render functions)
- 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);
}
Yes, I have created a pull request for this problem. https://github.com/vuejs/vue-next/pull/5197