Can I inject environment variables after bundle splitting?
Tags: webpack
Published: 2019-07-23 15:33:07

I'm working on a browser extension that I'm building with webpack and could use some help with environment variables. Hopefully I can explain what I'm trying to do properly here, but I'm more than happy to expand on anything if I'm not being clear!

TL;DR: I'm trying to get a list of files (strings of file names) that are dynamically created via webpack's bundle splitting process and inject that list of files as environment variables back into the code after the splitting/building happens.

First, here is a little background about browser extensions so that you can follow what I'm trying to do: Browser extensions need a `manifest.json` file that holds information about various files that the extension will use. A simple `manifest.json` file might look as follows:
```
{
    "manifest_version":2,
    "version":"1.2.3",
    "name":"My Cool Extension",
    "background":{
        "scripts":[
            "background-script.js"
        ]
    },
    "content_scripts":[
        {
            "js":[
                "content-script.js"
            ]
        }
    ]
}
```

Basically, `content-script.js` is a file that runs in a browser tab. When a user is navigating on webpages, this file can be injected and ran automatically. And `background-script.js` runs at the browser level and is a shared environment between all tabs. Extensions have a message passing system that allows the content script to communicate with the background script.

Through this communication, a content script can 'ask' the background script to inject additional code onto the browser tab. Let's call this additional code `additional-code.js`. The code that the background script runs to do this looks like this: `chrome.tabs.executeScript(tabId, { file: "additional-code.js" });`.

So, just to recap for a second, before I get into the webpack bits:
- Extensions need a `manifest.json` file.
- Content-script files run on a browser tab while background-script files run in the browser background and are shared by all of the tabs.
- Content scripts and background scripts can communicate through message passing.
- Background scripts can inject additional code into a browser tab through `chrome.tabs.executeScript`.

So, in webpack, you can do bundle splitting to break down big files into smaller files. For example, if my `additional-code.js` file was too big, it might be broken into `additional-code-1.js` and `additional-code-2.js`. Actually, the files get a crazy id at the end and look more like `additional-code-d939e436.js`. So they are a bit less predictable. Additionally, the number of files a bundle may be split into can change if the code changes. It's very dynamic.

The challenge here is for my background script code to know what these file names are so that it can properly inject all of the code it needs to when the tab asks for more code. In my case, the background script would have to be able to dynamically figure out which files the original `additional-code.js` file was broken out into. Depending on which environment I'm building for, this file may be broken out into a different number of bundles with different names. So it is highly dependent on the build.

So, that injection code might actually look like this.

```
// files === [ "additional-code-1.js", "additional-code-2.js" ]
files.forEach(file => {
    chrome.tabs.executeScript(tabId, { file }).
})
```

Using the `webpack-manifest-plugin`, I am able to hook into the build after the bundles are split. It's sorta intended to automatically build a list of files that webpack builds out, but using the `generate` function option that the plug-in takes, you can return your own format for the manifest file.

For example, I can dynamically add the files that are generated to the manifest by doing something like this:
```
// generate()
(seed, files) => {
    const { backgroundScripts, contentScripts } = generateScriptsFromFiles(files);
    const manifest = {
        manifest_version: 2,
        version: "1.2.3",
        name: "My Cool Extension",
        background: {
            scripts: backgroundScripts
        },
        content_scripts: contentScripts
    };
    return manifest;
};
```

To simplify the above, we can take the list of files that are generated and add the appropriate ones to the manifest. This includes our `content-script.js` and `background-script.js` but does not include `additional-code.js` since we want to inject `additional-code.js` manually and not inject it automatically via manifest magic.

However, this list of files does include all of the bundle-split files (`additional-code-1.js` and `additional-code-2.js`, for example), so it seems like a perfect hook for what I want to do. If I can take these file names and then re-inject them back into the code, then I would be set. However, by the time this generate function runs, the build is basically split and done. I don't see a way to go back and change files at this point.

Any help or guidance is greatly appreciated. This use-case is definitely on the edge of normal webpack workflows, so I'm happy to continue to elaborate or expand on anything here.