michmich112 / sveltekit-adapter-chrome-extension

Sveltekit adapter for making chrome extensions
MIT License
116 stars 16 forks source link

how to implement content scripts ? #26

Open sebmade opened 1 year ago

sebmade commented 1 year ago

I look for the best way to inject statically the content script. Because svelte can't generate specific js file, do I have to declare it in static directory ?

wallw-teal commented 1 year ago

I ran into the same issue. I'll probably try a PR here to improve support for this. In the meantime, here are some workarounds you can use to get started:

1. Static script

This just involves placing content.js or background.js in the static/ folder, and then referencing them from the manifest.json as "content.js" or "background.js". The downside is that the script isn't processed in any way and can't import anything. I don't really recommend this other than for incredibly small scripts.

2. Include in rollup options

vite.config.ts

import { fileURLToPath } from 'url';

const config: UserConfig = {
  // add this alongside the rest of your config
  build: {
    rollupOptions: {
      input: {
    background: fileURLToPath(new URL('./src/background.ts', import.meta.url)),
    content: fileURLToPath(new URL('./src/content.ts', import.meta.url)),
      }
    }
  }
};

(SvelteKit will yell at you during the build that it is going to replace this. I believe that's only if there is a collision between page names and the names in this config. There should not be any overlap and so it works out fine.)

This builds the files, but in build/app/immutable/content-<hash>.js. You could add a script after the build to move them, but they may include relative imports, so the best option is probably to replace the path in the manifest with the proper path to the script.

While the background script can use type: module (and do imports), the content script cannot. ~If you were to create a very thin content.js wrapper which just added a <script type="module" src="/path/to/actual-content-script"></script> tag to the page, that might work, but then every import through that entrypoint would need to be added to web_accessible_resources in the manifest (which may or may not be acceptable depending on what you're building).~ That doesn't work. I'm going with this method for the background script and option number 3 for the content script.

3. Process separately with esbuild/whatever

There's no particular reason you could not simply point esbuild at src/content.ts and have it output in build/ after the SvelteKit portion of the build. The downside here is that it won't share any code chunks with the rest of the application and thus will ship duplicate code. However, if that is negligible, this is probably the easiest way to get started.

michmich112 commented 1 year ago

I've been handling content scipts and service workers manually (through adding a .js file in the /static directory as @wallw-teal pointed out. It has been a clumsy and uncomfortable solution to say the least but the limitations placed on Manifest V3 make it necessary imo.

I have seen others use @wallw-teal 's 3rd solution in their project pretty successfully but there are still some friction points in that approach.

I'm very interested in brainstorming possible solutions to this problem as I think it would help a ton and with a stable version of Svelte Kit out, it would be the right time to think about it.

What solutions do you envision @sebmade @wallw-teal

wallw-teal commented 1 year ago

I'm going to separate the two for clarity:

Background Scripts / Service Workers

In manifest v3, you can specify

{
   "background": {
     "service_worker": "REPLACE_ME",
     "type": "module",
  }
}

and use the second method from my post above to get it built, then use a post build script to find it in build/app/immutable/background-*.js and replace REPLACE_ME with the path starting after build/. An easier way of having the adapter do this would be pretty slick. Alternatively, it could specifically configure the output to just go in build/<name-without-hash>.js so that the manifest could just statically have <name-without-hash>.js for the path.

Content Scripts

I don't know that you can do anything to get around method 3 above because there's no way (at least that I know of) to specify the content script as a module. All of the code-splitting/sharing/etc all uses ESM import and that immediately blows up when it runs. One interesting experiment might be to have the output for that specific script only use dynamic imports rather than top-level imports.

sebmade commented 1 year ago

I finally use another way with a combination of svelte / vite / CRX vite plugin started with this boilerplate. It works fine. Could we imaginate a sveltekit adapter reading a manifest.json in src and build it by using the reference generated (as it's done by CRX)?

B4Dmonkey commented 1 year ago

I've been trying to figure this out for the past week my self. I started with placing my content script in but ran into the limitation that if I try to import my lib in the content script the path isn't updated when being built. I'm going to try wallw-teal suggestion but I'm just wondering if its built into build/app/immutable/content-<hash>.js what would you put in the manifest? Is the hash changing every time? Does manifest accepts wildcards for the content script?

I'll also give sebmade solution a try.

michmich112 commented 1 year ago

I've been trying to figure this out for the past week my self. I started with placing my content script in but ran into the limitation that if I try to import my lib in the content script the path isn't updated when being built. I'm going to try wallw-teal suggestion but I'm just wondering if its built into build/app/immutable/content-<hash>.js what would you put in the manifest? Is the hash changing every time? Does manifest accepts wildcards for the content script?

I'll also give sebmade solution a try.

If you create a test project for wallw-teal's solution share it with me, we could add the ability of replacing the script names in the manifest file when its built.

B4Dmonkey commented 1 year ago

CRX

As I suspected theres a small flaw with wallw-teal's solution. When you build the hash changes. I'm sure you could change the built manifest, but I'm cautious of doing this since it's an extra step in the development process. Looking at the reference boilerplate that sebmade suggest it works fine, but the project structure is different from sveltekit I'm currently reading through the code to get an understanding of how its supposed to work and how the project structure is supposed to change as more things are added. It looks like CRXJS is the key here, perhaps it can be combined with sveltekit, I'm going to give that a try as well

wallw-teal commented 1 year ago

You can change the manifest with the resulting hashed files from the build like so (assuming you are using Manifest V3 and have the background script marked as type: "module"):

cd build; sed -i "s#\$BACKGROUND_PATH#$(find app -wholename '*/immutable/background-*.js')#g" manifest.json; cd -

However, I did note that you have this path in an above comment:

build/app/immutable/content-<hash>.js

That solution works for background scripts, not for content scripts (as I noted in my comment above) because I do not know of any way that a content script can run as a module script. For content scripts, I currently see no other choice than to point esbuild at your script entry after the Svelte Kit build.

My package.json scripts look like this:

{
  "scripts": {
    "dev": "run-p run:chrome watch",
    "watch": "watch 'pnpm build' src static",
    "build": "vite build --mode development && pnpm replace-background-path && pnpm build-content-script",
    "run:chrome": "web-ext run -t chromium -s build",
    "replace-background-path": "cd build; sed -i \"s#\\$BACKGROUND_PATH#$(find app -wholename '*/immutable/background-*.js')#g\" manifest.json; cd -",
    "build-content-script": "esbuild src/content.ts --bundle --minify --outdir=build",
    "preview": "vite preview",
    "test": "playwright test",
    "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
    "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
    "test:unit": "vitest",
    "lint": "prettier --plugin-search-dir . --check . && eslint .",
    "format": "prettier --plugin-search-dir . --write ."
  }
}

Note that the --mode development is just for easier debugging, and you would want to remove that in production.

wallw-teal commented 1 year ago

Update for content scripts:

You can use method 2 outlined above if you also have: static/content.js

(async () => {
  const src = chrome.runtime.getURL('$CONTENT_PATH');
  const module = await import(src);
  module.init(); // assuming it has export const init = () => ...
})();

and subsequently replace the path there.

Additionally, your scripts all need to be in web_accessible_resources in the manifest so that they will be allowed to load.

colecrouter commented 6 months ago

I came across a my own solution for content scripts based on @wallw-teal's method 2 & 3.

// package.json
...
"scripts": {
  "devext": "nodemon --watch src --watch static --exec \"vite build && esbuild src/content.ts --bundle --minify --outdir=build\" -e json,ts,css,html,svelte",
}
...
// src/content.ts
import { myFunc } from '$lib/index';

(async () => {
    console.log(await myFunc());
})();

Since you're letting ESBuild handle the file, you can also let it handle imports. As a result, it inlines the imported code, so no need to mess around in manifest.json. This seems to work in my cursory testing.