18

I am building a Chrome extension which adds some JavaScript to Wikipedia articles. As far as I know, the only way to use RequireJS is to add the line

<script data-main="scripts/bla" src="scripts/require-jquery.js>

However, in my Chrome extension, I don't have access to the HTML to add this line. Any suggestions?

Randomblue
  • 112,777
  • 145
  • 353
  • 547
  • Could you clarify that a bit more? The way you'd usually include modules with require.js would be via javascript, not a script tag: require(["some/module", "a.js", "b.js"]); – Julian Jan 29 '12 at 22:25
  • I know how to include modules, but how do I include requireJS *itself* after page-load, and using only JavaScript? – Randomblue Jan 29 '12 at 22:28
  • This is not a real answer to your question, but you can use simply create a greasemonkey script and execute all your javascript code [http://greasemonkey.mozdev.org/authoring.html](http://greasemonkey.mozdev.org/authoring.html) – Alberto Feb 10 '12 at 16:42
  • you could use CORS and load and run a script from another server somehow – Alexander Mills Aug 15 '15 at 03:41

4 Answers4

22

You do have access to the DOM of the page from the Chrome Extension via a content script, however the content script will only have access to the JavaScript objects created by the content script itself.

There are many ways to include scripts from a Chrome extension, how you include it will be based on what you plan to do with it.

If you want it in the popup page of a browser or page action you can either include it from the manifest as a content script or reference it using a script tag in the popup.html from a relative resource in your plugin.

From manifest:

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["jquery.js", "myscript.js"]
    }
  ],
  ...
}

From popup.html:

<script data-main="scripts/bla" src="scripts/require-jquery.js>

If you want it in the background page you can reference it from the background page using a script tag from a relative resource in your plugin.

From background.html

<script data-main="scripts/bla" src="scripts/require-jquery.js>

If you want it to be included in the browser page itself then you need to use dynamic script injection on the page's DOM. You have access to the page's DOM from a content script. Please note that if you load the JavaScript using this technique you plugin JavaScript (from a background page, content script or popup script) will not have access to it.

You can either load the requirejs from your extension using the chrome.extension.getURL method or from the a hosted location on the internet.

var script = document.createElement('script');
script.setAttribute("type", "text/javascript");
script.setAttribute("async", true);
script.setAttribute("src", chrome.extension.getURL("require-jquery.js"));  
//Assuming your host supports both http and https
var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
head.insertBefore(script, head.firstChild)
Adam Ayres
  • 8,732
  • 1
  • 32
  • 25
  • I have a content script for which the normal way to include it is using a `script` tag that has a `data-main` attribute. How can I specify the `data-main` attribute inside the manifest file? – Randomblue Jan 30 '12 at 10:31
  • No, there is no way to add an attribute tag on the content script if you are including it from the manifest. You might try using programmatic injection: http://code.google.com/chrome/extensions/content_scripts.html#pi – Adam Ayres Jan 30 '12 at 11:37
  • 1
    Actually, programmatic injection will not work either, same issues (will not allow attributes on script tag). I recommend that instead of using RequireJS you use AMD with Almond.js: https://github.com/jrburke/almond - be sure to use a requireJS optimizer first like r.js: https://github.com/jrburke/r.js – Adam Ayres Jan 30 '12 at 11:46
18

Here is how you can do it in the background page.

In manifest.json:

"background": {
    "scripts": [ "scripts/require.js","scripts/main.js"]
}, 

In main.js:

require.config({
    baseUrl: "scripts"
});

require( [ /*...*/ ], function(  /*...*/ ) {
    /*...*/
});

Instead of this:

<script data-main="scripts/bla" src="scripts/require.js></script>

You should use this:

<script src="scripts/require-jquery.js"></script>
<script src="scripts/main.js"></script>

See this question - Using Require.js without data-main

You can also do the same thing in the content page using this principle.

Nice Question by the way. I liked Adam Ayres's answers.

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
Nafis Ahmad
  • 2,689
  • 2
  • 26
  • 13
  • This should be the accepted answer, as it actually addresses the question. Also note that if you use a RequireJS plugin like jsx, you'll likely need to add `"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"` to the `manifest.json` file so that Chrome lets the extension use `eval()`. This can removed in a production release if you use r.js to bundle everything together. – jdunning Jul 22 '17 at 00:56
3

EDIT below is still true for plain vanilla require.js, but found a workaround by forking RequireJS

Github: https://github.com/jeroendelau/requirejs
Bower: bower install requirejs-for-browser-extensions

ORIGNAL POST

Injecting it into the content script is by far the hardest. And the above answers are incomplete or incorrect.

Background and Popup:
Go with earlier answer by @nafis, they will work

Content Script
This is a very tricky one, and this part from the api description is key:

Execution environment
Content scripts execute in a special environment called an isolated world. They have access to the DOM of the page they are injected into, but not to any JavaScript variables or functions created by the page. It looks to each content script as if there is no other JavaScript executing on the page it is running on. The same is true in reverse: JavaScript running on the page cannot call any functions or access any variables defined by content scripts.

Intuitively this should be correct
manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["requirejs.js", "myscript.js"]
    }
  ],
  ...
  "web_accessible_resources": [
     "js/*"
  ],
 ...
}

myscript.js

require(["myFancyModule"], function (FM) {
   ...
});

THIS WILL NOT WORK

The problem is that requirejs will proceed to load all your dependencies by injecting <script> tags in the header. These script tags are executed in the PAGE environment, not in the special EXTENSION environment. And this matters.

  1. Dependencies fail to load since requirejs is not loaded in the page environment
  2. If the website owner has alreay added requirejs, it migh clash
  3. You can decide to inject require.js into the page, as @Adam suggests, but in that case none of your extension features will work. storage, messaging, cross-site request are all not available

So in order for those to work, the modules loaded by requirejs need to be injected into the extensions environment. It is possible to use a requirejs plugin to change load behavior.

Because of the way this works the solution is very inelegant, AND it prevents your from seeing the scripts in the debugger under scripts. But if you're desperate, it will works.

myscript.js

/**
 * Inject the plugin straight into requirejs
 */
define("Injector", {
  load: function (name, req, onload, config) {

    //Load the script using XHR, from background
    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", function () {

      //Find depenencies in the script, and prepend the
      //Injector! plugin, forcing the load to go through this
      //plugin.
      var modified = getDeps(oReq.response)

      //have requirejs load the module from text
      //it will evaluate the define, and process dependencies
      onload.fromText(modified);
    });
    oReq.open("GET", req.toUrl(name) + ".js");
    oReq.send();

    //Find dependencies and prepend Injector!
    function getDeps(script)
    {
      //extract the define call, reduced to a single line
      var defineCall = script.match(/define([\s\S])*?{/m)[0].split("\n").join("");
      //extract dependenceis from the call
      var depsMatch = defineCall.match(/\[([\s\S]*?)\]/);

      //if there are dependencies, inject the injectors
      if (depsMatch)
      {
        var deps = depsMatch[0];
        var replaced = deps.replace(/(\'|\")([\s\S]*?)\1/g, '$1Injector!$2$1');
        return script.replace(/define([\s\S]*?)\[[\s\S]*?\]/m, 'define$1' + replaced);
      }
      //no dependencies, return script
      return script;
    }
  }
});

/**
 * Call all your dependencies using the plugin
 */
require(["Injector!myFancyModule"], function (FM) {
      chrome.storage.local.get({something});
});
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Jeroen de Lau
  • 640
  • 4
  • 13
  • Downvoting this as, whilst the answer may have been fine at one time, the repository created by the user hasn't had its fork re-based for several years. This inevitably means that there will be potential vulnerabilities that have appeared in the subsequent time. – Dan Atkinson Jul 12 '19 at 17:33
  • @DanAtkinson, fair enough, I have since moved on to webpack. In case someone stumbles across this, the description of the problem is still valid. Unless you pack all dependencies using r.js you have to find a way to load dependencies into the "isolated world"; – Jeroen de Lau Jun 12 '20 at 05:16
2

To use it in a content script you can just include it in the manifest.json as your first dependency:

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["requirejs.js", "myscript.js"]
    }
  ],
  ...
}

It will NOT pollute the global namespace (window) so no worries about that; its only available for extension scripts.

Also in manifest.json you have to name the files that requirejs will be able to use by naming them as "web_accessible_resources", to make this easy put them all in a folder (e.g. js/) so you can use a wildcard:

{
  "name": "My extension",
  ...
  "web_accessible_resources": [
     "js/*"
  ],
  ...
}

And then in your script (e.g. myscript.js) to use requirejs just ask for the dependencies using chrome.extension.getURL, like this:

requirejs([chrome.extension.getURL('js/library')], function(library) {
     ...
}
Ivan Castellanos
  • 8,041
  • 1
  • 47
  • 42
  • When I use this method, I can correctly load my additional modules, but then I get an error saying, `Uncaught ReferenceError: define is not defined`. Which makes sense, were you able to get this working? – Ian Jamieson Feb 05 '16 at 14:53