31

I've wrote a Chrome Extension. My background.js file is quite large, so I want to split it to smaller parts and load specified methods when required (some kind of lazy-loading).

I've done this with Firefox:

// ( call for load specified lib )
var libPath = redExt.basePath + 'content/redExt/lib/' + redExt.browser + '/' + libName + '.js';
var service = Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader);
service.loadSubScript("chrome://" + libPath);
// ( executing loaded file )

Is there any possiblity to do it similar way in Webkit-based browsers? I've found solutions for how to inject multiple JS files into matching pages (using manifest.json) but cannot find way to include JS file just for extension.

Tomasz Banasiak
  • 1,380
  • 2
  • 12
  • 19

5 Answers5

42

You can also do this the extremely easy way that is described here: https://developer.chrome.com/extensions/background_pages#manifest

{
  "name": "My extension",
  ...
  "background": {
    "scripts": [
      "lib/fileone.js",
      "lib/filetwo.js",
      "background.js"
    ]
  },
  ...
}

You won't be doing lazy loading, but it does allow you to break your code up into multiple files and specify the order in which they are loaded onto the background page.

Omn
  • 2,570
  • 1
  • 22
  • 37
  • I can see what you're trying to say with your answer, but it's hard to understand (what exactly are you referring to in that page?). A tiny code snippet would improve your answer a lot. As is, this answer is "link-only" and may be deleted. – Xan Jan 08 '15 at 15:34
  • @Omn what are the pros and cons of your method versus the way that rsanchez suggests above? – user5508297 Jul 29 '16 at 04:53
  • 7
    **Beware:** the files order in ```scripts``` matters! – Kamafeather Aug 27 '18 at 22:35
  • @Kamafeather Which is why the answer says that this allows you to "specify the order in which [the files] are loaded onto the background page." – Omn Sep 20 '18 at 05:37
16

If you want to load a javascript file in the context of your background page and want to avoid using eval, you can just add a script tag to your background page's DOM. For instance, this works if your files are present in the lib folder of your extension:

function loadScript(scriptName, callback) {
    var scriptEl = document.createElement('script');
    scriptEl.src = chrome.extension.getURL('lib/' + scriptName + '.js');
    scriptEl.addEventListener('load', callback, false);
    document.head.appendChild(scriptEl);
}
rsanchez
  • 13,535
  • 1
  • 29
  • 42
4

You could attempt to use web workers, perhaps. In your background.js, include:

var worker = new Worker('new-script.js');

Web workers can spawn new workers, and even have an importScript("new-script.js") method.

Web workers can tend to be very limited, however. Check here for a great article on Web workers.

I don't know if they would work, however. The other option is using XMLHTTPRequest (AJAX) to dynamically retrieve the script, and eval it. Over a non-HTTPS connection however, this is probably blocked due to man-in-the-middle attacks. Alternatively, you can force Chrome to eval script from a non-encrypted connection (BUT DON'T DO IT) by adding this to manifest.json:

"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"permissions": ["http://badpractic.es/insecure.js"]

See Load remote webpage in background page: Chrome Extension for a more in-depth discussion of the AJAX method.

So your options seem to be limited.

This of course loads them all at once:

{
  "name": "My extension",
  ...
  "background": {
    "scripts": ["background.js","background2.js","background3.js"] //and so on
  },
  ...
}
Community
  • 1
  • 1
mdenton8
  • 619
  • 4
  • 13
  • 1
    Oh, sorry, I didn't realize you wanted lazy loading. I'll go looking, sorry – mdenton8 Sep 06 '13 at 07:01
  • Allright, but I want to prevent loading all files at once. My extension sometimes doesnt use all of methods, so i want to load only what's currently necessary. In FireFox i've got `use` method (eg. `storage = myExtension.use('storage')` which loads `/lib/storage.js`) and want to create similar solution in Chrome/Safari :) – Tomasz Banasiak Sep 06 '13 at 07:01
  • I gave you some more suggestions. Check my answer again? -Matt – mdenton8 Sep 06 '13 at 07:10
  • Workers are some kind of solution, but their limitations disqualify them in my case - and also I would have to rewrite almost all code to use it as a worker services. But i've upvoted your answer, cuz if somebody is looking for this answer before writing any code it will be helpful. – Tomasz Banasiak Sep 06 '13 at 07:21
  • I appreciate the upvote. Perhaps the AJAX method would be more helpful to you? Have you tried it? – mdenton8 Sep 06 '13 at 07:32
1

Found possible solution. Theres a simple implementation of RequireJS main method require which uses JavaScript callback trace to find event for loading main extension file and binds executes it with extension context. https://github.com/salsita/browser-require/blob/master/require.js

It seems to work, but this solution has a few cons:

  • bug reports are reported in "line 1, column 1" because this solution injects code directly to func.call - debugging is very hard
  • Loaded JS files does not appear in console / chromebug
  • If current tab uses HTTPS Chrome will disallow evaling scripts, especially this from local context (file:///), so it sometimes just dont work as expected
Tomasz Banasiak
  • 1,380
  • 2
  • 12
  • 19
  • You're right, this is a really good extension. More or less a good implementation of the AJAX method. Of course, you can allow unsafe evaling of scripts but you know that doing that would be kind of a breach of trust for any users that download your extension. Best of luck. – mdenton8 Sep 06 '13 at 07:35
  • Just wondering, with the first issue, can't you just press F11 in Chrome developer tools to step into the next function call? – mdenton8 Sep 06 '13 at 07:39
1

What I've done isn't lazy loading but I load some files before the others when my chrome extension icon is clicked. In my case, I wanted my util files before the others that utilize them:

chrome.browserAction.onClicked.addListener(function() {
  chrome.tabs.executeScript(null, {file: "src/utils/utilFunction1.js"});
  chrome.tabs.executeScript(null, {file: "src/utils/utilFunction2.js"});
  chrome.tabs.executeScript(null, {file: "src/main.js"});
  chrome.tabs.executeScript(null, {file: "src/otherFeature.js"});
});
Ryan Walker
  • 534
  • 1
  • 8
  • 18