How best to handle content scripts of larger size?

The background/context of the question is that I’ve been trying to use an extension and a native C application to behave like a single-page desktop application that can run without being connected to the internet.

The browser serves as the user interface and, of course, for all internet functions when the user chooses to do so, and the C application is compiled with SQLite and handles the databases and interacts with the local file system in a manner that limits what it can reach, in the event someone hijacks the extension, if that is possible.

The user opens a local, empty HTML file into which the extension injects the CSS and JS files, which build all the UI components as instances of objects as needed. It is finally working well and I’m surprised at how quickly things can run and communicate between the extension and C program. If I had known this a couple years ago, I could have saved a lot of time and struggle.

Now that the operating structure is in place, I’m able to add in the code from all the individual components I’ve made as I fumbled and stumbled along the way; and that leads to the question.

The content script is growing in size quite a bit as the pieces are assembled. It’s a little over 1 MB and it might be close to 2 MB by the time it’s complete. The background script is kept as small as possible and primarily acts as a telephone operator between the content script and C program.

Should the content script be broken into separate smaller scripts, like header files in a C program? Is it okay to inject five smaller scripts when the extension loads, or better to keep it as one large script?

At 1 MB, it doesn’t seem to be an issue for the browser at all; it loads quickly and performs quickly. Is there a way to handle larger scripts that is best for browser performance?

I read a little about JS modules but that doesn’t appear to pertain to injecting scripts from an extension into a web page; but perhaps I am misunderstanding.

Thank you.

If it is your own HTML page, then you do not need content scripts and can use modules.

If it’s not your own HTML page and you need a content script, you can use a bundler like webpack, snowpack, rollup etc. to organise your JS into modules while developing.

In general, content scripts don’t have the issue of normal page scripts, where being big means that you rely on the network and make it longer for the actual page to be responsive. Generally loading a content script is fairly quick, since it will always happen from disk and never involve any network requests. Further, the JS runtime is built to optimize code that runs a lot well, and potentially never closely look at code that doesn’t end up running.
Generally, I’d suggest to not prematurely optimize in terms of content script size, as long as you can efficiently develop it and don’t exceed 4MB per JS file. This is to avoid the linter no longer attempting to parse the files.

Lastly, header files make little sense in JS, since you do not need to declare all your functions. Instead, JS is usually organized into modules, which let you contain units into separate files, which is more like the main code separation in files in C++ that is then joined again during linking, as opposed to header files.

Thank you very much for the answers and explanation.

Where you write that when it is one’s own HTML page, content scripts aren’t needed and modules can be used, would you elaborate a bit please?

Just in case the user adds a file by the same name, I put a GUID as a data attribute on the body element of the otherwise empty local HTML file. Through the manifest settings, a small content script is first injected that retrieves the GUID and passes it to the background script in the request to open a communication port. If the GUID is valid, the CSS and full JS file are added to the HTML file as below.

browser.tabs.insertCSS( id, { 'file' : 'css/file.css', 'runAt' : 'document_start' } )
  .then( () => { return browser.tabs.executeScript( id, { 'file' : 'js/file.js', 'runAt' : 'document_end' } ); } )
  .catch( serve_error )

How do modules replace this? Do you mean that, following the MDN document on modules, the content script can be broken into modules in which all is exported from each, and a small script that imports the modules would be injected?

If so, would this be expected to work on a local HTML page that the extension acts upon? Somewhere in that document, it warns that you can’t test locally because a file URL will throw a CORS error but it doesn’t discuss extensions.

As long as the text editor doesn’t get fussy about the file size, I can continue to work with one large program. I’m just curious to know now after reading your response. I’m not going to have a library of code from which only a portion is needed. All the code in the extension will be needed in the page. Likely, I’m not understanding the full purpose of modules.

Thank you.

If it is an extension HTML page you can use normal <script> tags to load scripts, and as such you can use esmodules natively.

They don’t. What you can do is use modules in your source code and transform them back into one or more content script files that then behave like normal script files. The main purpose of modules in this case is to break up your code to make it easier to reason about (i.e. clearly stating dependencies etc.) and also making it easier to unit test (which is why I used the word unit earlier).

Thank you for the added explanation.