67

I am trying to migrate my chrome extension from manifest version 2 to 3. Now that background scripts are replaced by service workers in manifest v3, I can no longer use a html file and refer js files in script tags.

Is there any way I can import my individual script files into the service_worker.js file?

I search almost everything on internet and couldn't find any solution. Even the official docs here Register background scripts were not so helpful. Any help would be appreciated.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
bhanu
  • 2,260
  • 3
  • 24
  • 35
  • Use the built-in importScripts function, see also [Web Workers - How To Import Modules](https://stackoverflow.com/a/45578811) – wOxxOm Feb 28 '21 at 07:37
  • Thanks @wOxxOm. It worked for me. I tried this yesterday but the service worker failed to start for some reason. – bhanu Feb 28 '21 at 10:31

1 Answers1

90

First off, important warnings:

  • Warning: Chrome 92 or older doesn't show errors occurred in the service worker - it was a bug, fixed in newer Chrome, which now shows the errors in chrome://extensions page. These old versions of Chrome can't register the background script if an unhandled exception occurs during its compilation (a syntax error like an unclosed parenthesis) or initialization (e.g. accessing an undefined variable), so if you still support old Chrome you may want to wrap the code in try/catch.

  • Warning: Chrome 92 or older requires the worker file to be in the root path (bug).

  • Warning! Don't import DOM-based libraries like jQuery or axios because service workers don't have DOM so there's no document, XMLHttpRequest, and so on. Use fetch directly or find/write a library that's based on fetch and doesn't use window or document.

0. NPM packages

Use a bundler like webpack.

1. ES modules in Chrome 92 and newer

Enabled by adding "type": "module" to the declaration of background in manifest.json.

  • Name must start with a path and end with an extension like .js or .mjs
  • Static import statement can be used.
  • Dynamic import() is not yet implemented (crbug/1198822).

manifest.json:

"background": { "service_worker": "bg.js", "type": "module" },
"minimum_chrome_version": "92",

bg.js:

import {foo} from '/path/file.js';
import './file2.js';

As noted at the beginning of this answer, if you still target Chrome 92 or older, which don't surface the errors during registration, each imported module should also use try/catch inside where an exception is possible.

2. importScripts

This built-in function synchronously fetches and runs the scripts so their global variables and functions become available immediately.

manifest.json:

"background": { "service_worker": "bg-loader.js" },

bg-loader.js is just a try/catch wrapper for the actual code in separate files:

try {
  importScripts('/path/file.js', '/path2/file2.js' /*, and so on */);
} catch (e) {
  console.error(e);
}

If some file throws an error, no subsequent files will be imported. If you want to ignore such errors and continue importing, import this file separately in its own try-catch block.

Don't forget to specify a file extension, typically .js or .mjs.

2b. importScripts inside a listener

Per the specification, we must use a service worker's install event and import all the scripts that we want to be able to import in an asynchronous event later (technically speaking, anything outside of the initial task of the JS event loop). This handler is called only when the extension is installed or updated or an unpacked extension is reloaded (because it's equal to an update).

It's this convoluted in MV3 because service workers were designed for the Web, where remote scripts may be unavailable offline. Hopefully, it'll be simplified in crbug/1198822.

See also: webpack-target-webextension plugin for WebPack.

const importedScripts = [];

function tryImport(...fileNames) {
  try {
    const toRun = new Set(fileNames.filter(f => !importedScripts.includes(f)));
    if (toRun.size) {
      importedScripts.push(...toRun);
      importScripts(...toRun);
    }
    return true;
  } catch (e) {
    console.error(e);
  }
}

self.oninstall = () => {
  // The imported script shouldn't do anything, but only declare a global function
  // (someComplexScriptAsyncHandler) or use an analog of require() to register a module
  tryImport('/js/some-complex-script.js');
};

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.action === 'somethingComplex') {
    if (tryImport('/js/some-complex-script.js')) {
      // calling a global function from some-complex-script.js
      someComplexScriptAsyncHandler(msg, sender, sendResponse);
      return true;
    }
  }
});
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • 1
    I get an error " importScripts() of new scripts after service worker installation is not allowed." – spez Apr 08 '21 at 07:02
  • I have some async methods whiting the imported scripts, is this not allowed? @wOxxOm – spez Apr 08 '21 at 07:27
  • 2
    see my post https://stackoverflow.com/q/67000104/12550657 – spez Apr 08 '21 at 08:27
  • Sadly I get for variant: `2. ES modules in Chrome 92 and newer` error `An unknown error occurred when fetching the script.`. The import is done: `import BuM from './lib/BackupsManager';` and BackupsManager is a default exporting a singleton instance of BackupsManager. Do you have any idea why this might happening @wOxxOm – Bogdan M. Sep 23 '21 at 11:48
  • 1
    By omitting `.js` file extension you're not using the ES module import syntax, which is why it fails. – wOxxOm Sep 23 '21 at 14:07
  • @wOxxOm THANK YOU! What a catch xD – Bogdan M. Oct 20 '21 at 11:34
  • What should I do if I still need to change the DOM in the background js in mv3? – mingchau Oct 24 '21 at 07:24
  • @mingchau, this is not related to the topic so please ask a new question and describe how you want to use DOM there or show us how you currently do it in your MV2 extension. – wOxxOm Oct 24 '21 at 08:09
  • trying ```import { sha256 } from "./web/utils.js";``` fails with ```Uncaught SyntaxError: Unexpected token '{'``` doesn't work, Inside functions – D. Sikilai Dec 30 '21 at 08:58
  • I'm getting errors trying to importScripts: `Error handling response: Error: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'chrome-extension:///scripts/jsencrypt.min.js' failed to load.` I'm importing it within the listener right before encryption function is called. – alcor8 Jan 18 '22 at 22:05
  • @alcor8, ask a new question with an [MCVE](/help/mcve). – wOxxOm Jan 18 '22 at 23:29
  • @wOxxOm I revised a question that was deemed a duplicate to this one. Could you please help me get it reopened? [link] (https://stackoverflow.com/questions/70715942) – alcor8 Jan 19 '22 at 18:53
  • For me jQuery is not working when I add it via background worker script for some reason but when I add another script that will interact with DOM elements I can use it and if I attach anything to window object, I can also access it via content script. – Muhammad Jun 19 '22 at 00:52
  • @wOxxOm does it call scripts in sequence? I want to load `jquery.js` before I use it in `background.js` – Volatil3 Jul 03 '22 at 16:26
  • @wOxxOm I just read your message about jQuerym how else can I use it in background? – Volatil3 Jul 03 '22 at 16:33