samrum / vite-plugin-web-extension

A vite plugin for generating cross browser platform, ES module based web extensions.
MIT License
325 stars 32 forks source link

Dynamic content scripts and popups #75

Closed Diggsey closed 1 year ago

Diggsey commented 1 year ago

Using this plugin it seems to be impossible to have it compile content scripts or popups without also including them in the manifest. For dynamic content scripts (injected with executeScript), they shouldn't be listed in the manifest, although they do need to be available at a known location at runtime. For the popup, if you do specify a popup in the manifest, then there's no way to receive the "onClicked" event for the browser action. I would like to be able to receive the event and then dynamically decide whether to show the popup (or do something else).

samrum commented 1 year ago

This functionality is already achievable by adding files to the web_accessible_resources manifest property.

HTML files and scripts that match the webAccessibleScripts filter plugin option are processed as inputs by the plugin.

Need to add some documentation around this since it seems to be missing, but that would be the plugin's way to do what you want.

Diggsey commented 1 year ago

Hmm, I couldn't seem to get that option to do the right thing (generate the files in the right place). Maybe I was using it incorrectly.

samrum commented 1 year ago

Sorry for the late follow up, but when you say it didn't generate files to the right place, can you give an example?

Diggsey commented 1 year ago

Well, I added this to the plugin config:

webAccessibleScripts: {
    include: "src/entries/content/main.ts"
}

And I expected a file to be generated at dist/src/entries/content/main.js when I built the project. This did not happen, nor did any new files appear in dist/src/entries/.

When I added this instead, using my modifications to the plugin:

extraContentScripts: [
    {
        js: ["src/entries/content/main.ts"]
    }
],

Then that file was generated on build.

samrum commented 1 year ago

Ah, ok, I see, just a bit of confusion over that plugin option.

The existing way to do what you want would be to add src/entries/content/main.ts to the web_accessible_resources property of your manifest.

The webAccessibleScripts plugin option defines the filter that gets ran on web_accessible_resources during builds to decide whether that file should be processed as an input. The default filter will already handle TypeScript files, so you wouldn't need to change it.

Diggsey commented 1 year ago

Ah yes that does seem to work. Although, it does mean that these files are made web-accessible, which is not great...

samrum commented 1 year ago

True, but chunks from regular content scripts are automatically added as web accessible resources as well, so I don't think that should be much of an issue? In Manifest V3, the use_dynamic_url property gets added to generated web accessible resources which would limit fingerprinting if that's a concern.

Diggsey commented 1 year ago

The chunks are, but the predictably named entry point is not 🤷

samrum commented 1 year ago

Hmm, also true. Is there a reason to have separate options like you have in your PR other than ease of integration with the existing code?

I'm thinking maybe the better overall solution would be to support an option that lets you add whatever additional files you want to be processed that would replace webAccessibleScripts and the implicit processing of web_accessible_resources for html files that's happening right now. Could support options per file for specifying whether they should be web accessible, match patterns, etc.

I have some existing work in progress that was going to expand file support for webAccessibleScripts (like for CSS processing) that I could repurpose for that.

Diggsey commented 1 year ago

I just went with the easiest to implement option for someone unfamiliar with vite (or webpack for that matter). I'm sure there are better approaches.

iwaduarte commented 1 year ago

For anyone stumbling upon it, the configuration should be:

On your manifest js:

const manifest = {
  ...other properties
  web_accessible_resources: ["src/path/file.extension"],
};

In case you are using React like myself your file(s) will have a jsx extension, therefore in your vite.config.js should also have:

export default defineConfig({
  plugins: [
    react(),
    webExtension({
      manifest,
      webAccessibleScripts: {
        include: /\.([cem]?js|jsx|ts)$/,
      },
    }),
  ],

Notes: 1- You will only need to declare inside web_accessible_resources the entry point file when using React(jsx) no need to add the dependencies in my case (main.jsx) 2-There is a small bug that leaves the "src/path/file.extension" inside the manifest.json even though the data name is dynamically hashed. 3- Note the jsx on include. Without it, my files weren't being created.

aleksolutions commented 1 year ago

There are other problems with that configuration. That should work just fine if you are only using Manifest V2.

In case you are using Manifest V3 and Manifest V2 at the same time (for cross browser extension) you should set it like this:

const sharedManifest = {
  // your common manifest settings
}

const webAccessibleResources = ['path/to/your/entry.extension']

const ManifestV2 = {
  ...sharedManifest,
  ...other Manifest V2 specific props,
  web_accessible_resources: webAccessibleResources
}

const ManifestV3 = {
  ...sharedManifest,
  ...other Manifest V3 specific props,
  web_accessible_resources: [
    {
      resources: webAccessibleResources,
      matches: ['https://*.host1.net/*', 'https://*.host2.com/*']
    }
  ]
}

And if you are also working with hmr, you should know that at least in my case (using a file with .ts extension), when using hmr, my output, manifest and file are converted to .js but in the build it keeps it as it is (.ts)

So I had to create a function to inject my files to the web:

import browser from 'webextension-polyfill'

export const injectScript = (filePath: string) => {
  const extension = import.meta.hot ? 'js' : 'ts'
  const pathExtension = filePath.split('.').pop()
  if (pathExtension !== extension) {
    filePath = filePath.replace(`.${pathExtension}`, `.${extension}`)
  }

  const node = document.getElementsByTagName('head')[0]
  const script = document.createElement('script')
  script.setAttribute('type', 'text/javascript')
  script.setAttribute('src', browser.runtime.getURL(filePath))
  node.appendChild(script)
}

Hope it helps