Question

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?

Était-ce utile?

La solution

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)

Autres conseils

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.

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});
});

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) {
     ...
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top