Tampermonkey / tampermonkey

Tampermonkey is the most popular userscript manager, with over 10 million users. It's available for Chrome, Microsoft Edge, Safari, Opera Next, and Firefox.
GNU General Public License v3.0
4.2k stars 418 forks source link

Make @require able to point to other userscripts #853

Open StephGbzh opened 4 years ago

StephGbzh commented 4 years ago

Expected Behavior

@require allows to reference another userscript. I'm tired of copy-pasting code between similar (but not exactly the same) scripts, then updating one of them with a new functionality and copy-pasting again. If we had the possibility to invoke userscripts from each other, I could build something like small js libraries with utility methods I would write only once and @require them in scripts which need those methods.

Actual Behavior

I created a script named "mylibrary" and another called "test" When going on the site where "test" runs, I get this in the console: /!\ Tampermonkey: couldn't load @require from forbidden URL mylibrary

Specifications

Script

// ==UserScript==
// @name         mylibrary
// @namespace    http://tampermonkey.net/
// @version      1.0
// @author       You
// @include      /^file:///not-existing-url-at-least-i-hope-so//
// @grant        none
// ==/UserScript==

console.log("text from library")
// ==UserScript==
// @name         forum.tampermonkey.net
// @namespace    http://tampermonkey.net/
// @version      1.0
// @author       You
// @include      /^https://forum.tampermonkey.net//
// @require      mylibrary
// @grant        none
// ==/UserScript==
momocow commented 4 years ago

Enabling @requireing another userscript may not be a good idea to TamperMonkey, e.g. what about the @includes and @excludes defined in the library script if they conflict with the ones in the userscripts?, which may also break the current behavior of @require.

It would be better to use @require with "plain" javascript files, and you can host your library on a CDN if it is intended to be shared to public or just host it on your localhost server and @require it via http://localhost/uri.

Besides, you can also @require via a local file:/// URI if you've enabled "Local file access" at Chrome's extension management page or Tampermonkey's settings page (if you're using Firefox).


However, if there is a way to define dependencies between userscripts sounds good to me.

The new syntax can be something like @depend-on mylibrary, and the userscript with this header field will be executed only if all its @includes, @matchs and @excludes are met and only after its dependency userscript mylibrary has been executed. If mylibrary is not defined to run in this web page via its @includes, @matchs and @exclude, then the dependant userscript is not run, either.

StephGbzh commented 4 years ago

I'm all for not having a header in those library files indeed. I just put them in the example because it was a try based on the "official" example https:web.archive.org/web/20180219141256/https://forum.tampermonkey.net/viewtopic.php?p=1832#p1832 (link to an archive of the forum since the real forum is closed for now, also sorry for the formatting of the link, Github butchers it otherwise)

I'm not sure the absence of a header would be a problem: you can already @require jquery for example, and jquery does not have any Tampermonkey header.

Or maybe it could be done so that the header of any @required script is simply ignored and viewed as simple comments since the real header is already read from the main userscript.

DrLightman commented 3 years ago

Definitely a feature that would come handy.

But I think TM should define something that is a library rather than the possibility to include a normal script.

This of course carries some work to do as test if library exists prior execution, error cases managament, et cetera. Therefore I think they won't add it ;)

JAGulin commented 3 years ago

Original poster might consider closing this issue. As mentioned, "require" is already available to reuse libs. The error message was just pointing out that "mylibrary" wasn't a working URI. GreasyFork or GitHub would work fine for publishing the lib itself.

it could be done so that the header of any @required script is simply ignored

This is most likely the way it works already. The "header" of the main script is interpreted to know which "requires" to access, but when done any other comment is just a comment.

test if library exists prior execution, error cases managament

I think this is also how it works today. If anything "required" isn't available, I think the user-script won't run at all (as in the error in the main post). And if it would, then it probably fails on exception.

Note: TM will prepend the libs to the beginning of the script, it won't try to execute it, that is the job of the browser engine.

However, if there is a way to define dependencies between userscripts sounds good to me.

I first didn't understand why you'd want that, but I guess defining the order of execution is the real feature. This can be somewhat controlled by "run at" in script header https://www.tampermonkey.net/documentation.php#_run_at and more detailed by manual "ordering" for each user install https://www.mturkcrowd.com/threads/how-to-change-execution-order-of-userscripts-and-customize-excluded-pages.152/ I'm linking to external instructions, because I didn't find any mention in TM help. Note for comparison that the support in GM may have been taken out. https://github.com/greasemonkey/greasemonkey/issues/2679 What you don't have is a way to run only if other script is installed and a way to control it from script. Both these cases I think would be quite complex in the general case and not a very often used feature. You could probably set up some control mechanism in the actual script code. If you still want this I suggest you create a separate feature request with specific requirements and motivation.

StephGbzh commented 3 years ago

Original poster might consider closing this issue. As mentioned, "require" is already available to reuse libs.

I am well aware of @require, in fact it's even the first thing I mentionned in my original post. But it does not answer my use case since it does not allow to invoke other local userscripts.

GreasyFork or GitHub would work fine for publishing the lib itself.

And that's where it does not answer my use case: I don't want to publish anything anywhere. I want to keep my userscripts and libs local and private. What I am asking for is a way to @require local userscripts from other local userscripts.

Your link to the short doc of require gave me hope with the tampermonkey:// protocol but alas it serves only the few libs embedded in the Tampermonkey extension itself as confirmed by @derjanb.

The error message was just pointing out that "mylibrary" wasn't a working URI.

I would be delighted to use a working URI with whatever custom protocol that would point to another local userscript e.g.: tampermonkeyscripts://mylibrary.user.js

7nik commented 3 years ago

It's a bad feature, at least in the proposed way, because it allows defining dependencies without specifying how to resolve them. So the end user can download a userscipt, but it won't work because it requires some libs.user.js script, and do you think the user will know how to fix it or be happy with "to make this script work, you also need to install the following scripts...". No, he won't be happy with such a "feature".

As for your problem, in chromium-based browsers, you can require local files. So just add something like @require file:///path/to/project/shared_libs/mylib.js for mac/linux or @require file://D:/path/to/project/shared_libs/mylib.js for windows.

StephGbzh commented 3 years ago

It's a bad feature, at least in the proposed way, because it allows defining dependencies without specifying how to resolve them. So the end user can download a userscipt, but it won't work because it requires some libs.user.js script, and do you think the user will know how to fix it or be happy with "to make this script work, you also need to install the following scripts...". No, he won't be happy with such a "feature".

This doesn't make sense. If one ever wants to publish a script with a local @require, the obvious way is to also publish the library it depends upon and transforms the @require to point to some https link on Github or elsewhere. Another easy solution would be to publish the lib and the script. The user already clicked on one link to install the userscript, I don't see how he would crumble under the "difficulty" of clicking on a second link to install another script...

As for your problem, in chromium-based browsers, you can require local files. So just add something like @require file:///path/to/project/shared_libs/mylib.js for mac/linux or @require file://D:/path/to/project/shared_libs/mylib.js for windows.

As mentioned in the original post, I use Firefox so I can't use that (just tried though, but nope :( ).

7nik commented 3 years ago

Then setup any simplest local server to access scripts via localhost.

StephGbzh commented 3 years ago

Then setup any simplest local server to access scripts via localhost.

That's a possible workaround but with multiple drawbacks:

I still think that feature would be useful and several bricks are already there to pave the path:

Note that I am not saying it's easy to develop, that I don't know !

bo33b commented 2 years ago

I want to throw support behind this feature.

I use Chrome, and I maintain my scripts solely within the TM script editor. About a dozen of my scripts share 3 functions which I've pasted at the top. Every time I revise one of those functions, I need to copy the change to a dozen other scripts to maintain consistency. Not cool.

Yes... it's possible to @require file://... a .js resource saved to disk as suggested, when using Chrome, and given the proper permission. However the workflow for opening, editing and saving a script is thus:

    1.  Open TM Dashboard
    2.  Click 'Utilities' Tab
    3.  Click 'Choose File' under File Import
    4.  Brose for .user.js file, click 'Open'
    5.  Click 'Install'
    6.  Switch to 'Installed Userscripts' Tab
    7.  Hit F5 or refresh the page to see the imported script
    8.  Click the name of the imported script (or Edit button)
    9.  Make changes
    10. Click 'File' in the Editor
    11. Click 'Save to disk'
    12. Check the filename to overwrite and click 'Save'
    13. Click 'Yes' to confirm overwrite the file

I can almost copy-paste 12 times in the time it takes to do that. Maybe if it was as easy to open a script from disk as it is to create a new one I would be satisfied with working that way, but as it is now the TM editor is not optimized for working with files at all.

7nik commented 2 years ago

I have a feeling that you're doing it in a very wrong way or I don't get something. If you made them for yourself only, then keep them all in the TM and just make backups (though still need copy-paste multiple times). If you made them for others too, then you anyway need to host them somewhere, so move out the function to an additional function which you @require in each userscript.

timgoodman commented 2 years ago

I think I'm in a similar boat to some of the above commenters asking for this feature. I have a bunch of scripts that I wrote for my own personal use, to modify websites I visit in various ways that make them more accessible for me. I have a collection of functions I wrote (mostly DOM manipulation functions, but designed more for my specific use cases and preferences than something like jQuery) that I use in each of these scripts, and I've pasted these at the top of all my scripts. So, it's the antithesis or DRY - if I change this library of functions, I have to copy the changes into every single script to keep them all consistent. It would be much more convenient if I could just put this library in one userscript (or some other script file managed within Tampermonkey) and have my other scripts @require that one.

The nice thing about having it all in Tampermonkey is that if I switch to a different PC I can just restore the whole thing from a single Tampermonkey backup file and it all works immediately. Having to host it all on the web or set up a local server on each new PC is a good bit less convenient. (I could copy my library of functions to each PC and reference it with "file:///", but as mentioned above that has its own limitations.)

If there's a simple way to do this that's already supported, I'm all ears.

DrLightman commented 2 years ago

@timgoodman ... Having to host it all on the web ...

I think this is the correct way to do that, like you would @require jQuery, you also @require your hosted-somewhere library. Backup/restore, modification and publish of the library are the drawbacks but the real world can't be perfect, probably the other request opens issues which outweight those drawbacks.

lfilho commented 2 years ago

Another +1 here. My use case is corporate userscripts, so I can't host them publicly. And spinning a server up would involve also having to deal implement authentication, authorization... So the only current way possible is distributing the files via download + manual installation on TM. That means that we will never have an https:// to consume libraries from, so importing other locally installed scripts would be ideal :)

tfiers commented 1 year ago

There is a workaround, using // @grant unsafeWindow:

It works great, I can now define functions in one userscript and call them in another.

bo33b commented 1 year ago

There is a workaround, using // @grant unsafeWindow:

It works great, I can now define functions in one userscript and call them in another.

Anything you attach to unsafeWindow will be exposed to the website's javascript code and thus to the website's administrator, who could then view your code and potentially exploit it, possibly allowing them to upload files from your PC or local network. Or maybe they'd make changes to the site which break your script. If unsafeWindow works for you then great but beware of the risks.

emp2687 commented 1 year ago

I have a very similar request to this discussion. I have a few scripts with shared functionality that are included into my user scripts. What I want is to:

  1. Be able to require these shared scripts from files.
  2. On different computers the scripts can be placed in different directories, so I can't just do @require file:/somedirectory/file1.js because it could be in a different place on different computers.
  3. I do not want to host these files anywhere. If the hosting service is down, I don't want my tampermonkey to break.
  4. I do not want to have to host it locally. It seems silly to spend resources running a web server just to serve a few files which are already present and just need to be read.

So, ultimately what I want is a syntax similar to this: @require userscript://myscriptlib/script1.js @require userscript://myscriptlib/script2.js

vermgit commented 1 year ago

I'd also need this feature (the reasons were mentioned above). However, I'd rather use it like @resource which allows me to load the file wherever I want and do whatever I want to do with it (note that it doesn't necessarily have to be Javascript-code). OK, eval() is evil, but who owns the imported file? Correct, I do, so that's not a reason to not have it.

A scheme like "userscript" or "tampermonkey" in the URL would make sense.

cow1337killer3 commented 1 year ago

Can't you guys just create a new user script with:

Then to import it in another userscript, just use GM_getValue and eval() the string to get the function, then call the function to get the object with your library functions.

cow1337killer3 commented 1 year ago

But besides that, we should just be able to require userscripts, and any required userscript will have the match/include/exclude tags just be completely ignored.

emp2687 commented 1 year ago

Can't be done. Stored values are not shared between scripts.

On Sat, Aug 12, 2023, 4:44 PM cow1337killer3 @.***> wrote:

Can't you guys just create a new user script with:

  • @match :///*
  • 1st in userscript execution order
  • has a single function that returns an object containing all of your library functions, then you get the source of that function with .toString() and store it using GM_setValue

Then to import it in another userscript, just use GM_getValue and eval() the string to get the function, then call the function to get the object with your library functions.

— Reply to this email directly, view it on GitHub https://github.com/Tampermonkey/tampermonkey/issues/853#issuecomment-1676131422, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALPBITWRGXLVG3ZGFJHME2TXVABMJANCNFSM4KDKA3ZA . You are receiving this because you commented.Message ID: @.***>

cow1337killer3 commented 3 months ago

Ummm... my comments and code just got deleted?