Which approach would work best really depends on the specific needs of the project. Context would determine which one I'd use.
Undefining define
Temporarily
I'm mentioning it because you tried it.
DON'T DO THIS!
The approach of undefining define
before you load your script and restoring it after is not safe. In the general case, it is possible for other code on the page to perform a require
call that will resolve after you've undefined define
and before you've defined it again. After you do document.body.appendChild(s);
you're handing back control to the JavaScript engine, which is free to immediately execute scripts that were required earlier. If the scripts are AMD module, they'll either bomb or install themselves incorrectly.
Wrapping the Script
As Dheeraj V.S. suggests, you can wrap the script to make define
locally undefined:
(function(define) { /* original module code */ }())
can work for trivial cases like the one you show in your question. However, cases where the script you try to load actually has dependencies on other libraries can cause issues when it comes to dealing with the dependencies. Some examples:
The page loads jQuery 2.x but the script you are trying to load depends on a feature added in jQuery 3.x. Or the page loads Lodash 2 but the script needs Lodash 4, or vice-versa. (There are huge differences between Lodash 2 and 4.)
The script needs a library that is not otherwise loaded by something else. So now you are responsible for producing the machinery that will load the library.
Using RequireJS Contexts
RequireJS is capable of isolating multiple configurations from one another by defining a new context. Your bookmarklet could define a new context that configures enough paths for the script you are trying to load to load itself and its dependencies:
var myRequire = require.config({
// Within the scope of the page, the context name must be unique to
// your bookmarklet.
context: "Web Designer's Awesome Bookmarklet",
paths: {
myScript: "https://...",
jquery: "https://code.jquery.com/jquery-3.2.1.min.js",
},
map: {...},
// Whatever else you may want.
});
myRequire(["myScript"]);
When you use contexts like this, you want to save the return value of require.config
because it is a require
call that uses your context.
Creating a Bundle with Webpack
(Or you could use Browserify or some other bundler. I'm more familiar with Webpack.)
You could use Webpack to consume all the AMD modules necessary for the script you are trying to load to produce a bundle that exports its "module" as a global. At a minimum, you'll need something like this in your configuration:
// Tell Webpack what module constitutes the entry into the bundle.
entry: "./MyScript.js",
output: {
// This is the name under which it will be available.
library: "MyLibrary",
// Tell Webpack to make it globally available.
libraryTarget: "global",
// The final bundle will be ./some_directory/MyLibrary.js
path: "./some_directory/",
filename: "MyLibrary.js",
}
Once this is done, the bookmarklet only needs to insert a new script
element that points to the produced bundle and no longer has to worry about wrapping anything or dealing with dependencies.
define
local to that function. – Trigeminal(function(define){
...})();
. You can post the answer and I will accept it if I don't find something simpler. – Generous<script>
element with thetext
attribute. – Generous