Code Monkey home page Code Monkey logo

vite-plugin-web-extension's Introduction

@samrum/vite-plugin-web-extension

npm version node compatibility ci

Generate cross browser platform, ES module based web extensions.

  • Manifest V2 & V3 Support
  • Completely ES module based extensions
    • Including in content scripts!
  • Vite based html and static asset handling
    • Including in content scripts!
  • HMR support
    • All Manifest entry points
      • Including content scripts when using a Chromium browser!
    • CSS styles in content scripts
      • Including shadow DOM rendered content!
    • Including Manifest V3 support since Chromium 110!

Quick Start

Create a new Vite web extension project

npm init @samrum/vite-plugin-web-extension@latest

Supports choice of Manifest version, TypeScript support, and framework (Preact, React, Solid, Svelte, Vanilla, Vue).

Check the README of the generated extension for usage information.

Usage

Requires Vite 3+

npm install @samrum/vite-plugin-web-extension

Examples

Manifest V2

vite.config.js:

import { defineConfig } from "vite";
import webExtension from "@samrum/vite-plugin-web-extension";

export default defineConfig({
  plugins: [
    webExtension({
      manifest: {
        name: pkg.name,
        description: pkg.description,
        version: pkg.version,
        manifest_version: 2,
        background: {
          scripts: ["src/background/script.js"],
        },
      },
    }),
  ],
});
Manifest V3

vite.config.js:

import { defineConfig } from "vite";
import webExtension from "@samrum/vite-plugin-web-extension";

export default defineConfig({
  plugins: [
    webExtension({
      manifest: {
        name: pkg.name,
        description: pkg.description,
        version: pkg.version,
        manifest_version: 3,
        background: {
          service_worker: "src/background/serviceWorker.js",
        },
      },
    }),
  ],
});
Firefox Experimental Manifest V3 There are two configurations an extension needs to make for experimental manifest V3 support:
  1. Background service workers are not supported, so you are required to use a background script.

  2. The use_dynamic_url property is not supported for web accessible resources. In the plugin options, set useDynamicUrlWebAccessibleResources to false:

      webExtension({
        ...
        useDynamicUrlWebAccessibleResources: false,
      }),
Devtools To add content to the browser dev tools, add `devtools_page` to your manifest
devtools_page: "src/entries/devtools/index.html",

Place a script devtools.js in public dir.

var _browser;
if (chrome) {
  _browser = chrome;
} else {
  _browser = browser;
}
_browser.devtools.panels.create(
  "My Panel", // title
  "images/icon-16.png", // icon
  "src/entries/devtools/index.html" // content
);

Then load the script from your devtools html which placed in src/entries/devtools/index.html.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Devtools</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="./main.ts"></script>
    <script src="/devtools.js"></script>
  </body>
</html>

Options

manifest

  • The manifest definition for your extension
  • All manifest property file names should be relative to the root of the project.

useDynamicUrlWebAccessibleResources (optional)

  • Type: boolean
  • Default: true
  • Adds the use_dynamic_url property to web accessible resources generated by the plugin

optimizeWebAccessibleResources (optional)

  • Type: boolean
  • Default: true
  • On build, in Manifest V3, merge web accessible resource definitions that have matching non-resource properties and dedupe and sort resources. In Manifest V2, sort web accessible resources.

additionalInputs (optional)

  • Type:

      type AdditionalInput =
        | string
        | {
            fileName: string;
            webAccessible?:
              | boolean
              | {
                  matches: string[];
                  extensionIds?: string[];
                  excludeEntryFile?: boolean;
                }
              | {
                  matches?: string[];
                  extensionIds: string[];
                  excludeEntryFile?: boolean;
                };
          };
    
      additionalInputs?: {
        scripts?: AdditionalInput[];
        html?: AdditionalInput[];
        styles?: AdditionalInput[];
      };
  • Additional input files that should be processed and treated as web extension inputs.

  • Useful for dynamically injected scripts or dynamically opened HTML pages.

  • The webAccessible option configures whether the input file and its dependencies are included in the manifest web_accessible_resources property. Defaults to true.

    • When set to true, defaults to:
        {
          matches: ['<all_urls>'],
          excludeEntryFile: false,
        }
    • The excludeEntryFile option prevents the entry file from being added as a web accessible resource. Defaults to false.
  • Example

      webExtension({
        manifest: ...,
        additionalInputs: {
          scripts: [
            'src/entries/webAccessibleScript.js', // defaults to webAccessible: true
            {
              fileName: 'src/entries/webAccessibleScript2.js',
              webAccessible: true,
            },
            {
              fileName: 'src/entries/privateScript.js', // entry file and dependencies are not web accessible
              webAccessible: false,
            },
            {
              fileName: 'src/entries/entryFileExcluded.js', // entry file is not web accessible and dependencies are
              webAccessible: {
                matches: ['<all_urls>'],
                excludeEntryFile: true,
              },
            },
          ],
        },
      })

Content Scripts

  • For HMR style support within shadow DOMs, use the addStyleTarget function to add the shadowRoot of your element as a style target:

    if (import.meta.hot) {
      const { addViteStyleTarget } = await import(
        "@samrum/vite-plugin-web-extension/client"
      );
    
      await addViteStyleTarget(appContainer);
    }
  • For builds, use the import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS variable to reference an array of CSS asset paths associated with the current output chunk.

TypeScript

In an env.d.ts file, add the following type reference to define the plugin specific import.meta variables as well as plugin client functions:

/// <reference types="@samrum/vite-plugin-web-extension/client" />

Browser Support

The following requirements must be met by the browser:

  • Must support dynamic module imports made by web extension content scripts.
  • Must support import.meta.url

A sample of supported browsers:

Manifest V2 Manifest V3
Chromium 64 91
Firefox 89 N/A (In development)

The plugin will automatically default vite's build.target config option to these minimum browser versions if not already defined by the user.

For dev mode support in Manifest V3, Chromium version must be at least 110.

How it works

The plugin will take the provided manifest, generate rollup input scripts for supported manifest properties, then output an ES module based web extension.

This includes:

  • Generating and using a dynamic import wrapper script in place of original content scripts. Then, moving the original scripts to web_accessible_resources so they are accessible by the wrapper script. Needed because content scripts are not able to be loaded directly as ES modules.
    • This may expose your extension to fingerprinting by other extensions or websites. Manifest V3 supports a use_dynamic_url property that will mitigate this. This option is set for manifest V3 web accessible resources generated by this plugin.
  • Modifying Vite's static asset handling to maintain import.meta.url usages instead of rewriting to self.location. Needed so content script static asset handling can function correctly.
  • Modifying Vite's HMR client to add support for targeting specific elements as style injection locations. Needed to support HMR styles in shadow DOM rendered content.

Why this is a Vite specific plugin

The plugin relies on Vite to parse and handle html files in addition to relying on Vite's manifest generation in order to map generated files to the eventual extension manifest.

Development

This project uses pnpm for package management.

Lint

pnpm lint

Tests

pnpm test

Build

pnpm build

vite-plugin-web-extension's People

Contributors

blake-regalia avatar detachhead avatar haruleekim avatar kent013 avatar maltoze avatar mdp avatar nathancharles avatar samrum avatar thanhlmm avatar woshizhazha120 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

vite-plugin-web-extension's Issues

Safari browser.runtime.onInstalled not working

Describe the bug
Hi guys, we are developing an extension and we need to open a new tab when a user installs the extension.

That's exactly what this boilerplate https://github.com/antfu/vitesse-webext does in background/main.ts:
browser.runtime.onInstalled.addListener((): void => {
console.log('Extension installed')
})

But apparently Safari doesn't fire the event(the listener is created correctly but never is called), we have done some tests using webpack for build the extension and it does fire the event, the code is almost the same for webpack and vite.

We don't know where the problem could be, if it's in the vite plugin or in safari itself

Reproduction
We are using this base repo https://github.com/antfu/vitesse-webext we follow the README instructions, when we build the project after that, we use: xcrun safari-web-extension-converter --app-name vittesse --macos-only . To transform the extension for safari, we run the extension in Safari and the event doesn't fire.

System Info
System:
OS: macOS 12.6.3
CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Memory: 185.71 MB / 16.00 GB
Shell: 3.4.1 - /usr/local/bin/fish
Binaries:
Node: 18.7.0 - /usr/local/bin/node
Yarn: 1.22.18 - /usr/local/bin/yarn
npm: 8.15.0 - /usr/local/bin/npm
Browsers:
Safari: 16.4

CSS issue if importing some svelte in contentScript AND in options

I wanted to have a common TabBar.svelte used in the contentScript app as well as in the options App.

So the file architecture look like

/entries/contentScript/primary/App.svelte (import ~/lib/TabBar.svelte)
/entries/options/App.svelte (import ~/lib/TabBar.svelte)
/lib/TabBar.svelte

The issue is as soon as the options App.svelte import the TabBar, the contentScript/primary/main.z2EEd.css is not longer generated upon build.
For know I'm duplicating the file to prevent this issue, I've no clue how to fix this correctly though.

My repo is here: https://github.com/HugoGresse/app-store-review-templates
You'll need to import the App.svelte from the contentScript into options/App.svelte

Maximum call stack size exceeded

Hi @samrum

I have an error trying to build a large extension, I know is an stupid Atlassian package that have over 20mb but would be great if you can fix it.

the output i'm getting is:

vite v3.1.8 building for production...
โœ“ 7285 modules transformed.
rendering chunks (273)...[webExtension] Maximum call stack size exceeded

And the function causing the problem is a recursive one which is on .../@samrum/vite-plugin-web-extension/dist/index.js

getMetadataforChunk(chunkId, bundle, includeChunkAsAsset, metadata = {
        css: new Set(),
        assets: new Set(),
    })

I tried to change the forEach loops with basic loops to see if I can pass the build, but with no luck.

Can you take a look at it?

Thank you very much!

Manifest TypeError: Cannot read property 'importedCss' of undefined

I am getting an error when trying to build my extension:

rendering chunks (11)...[webExtension] Cannot read property 'importedCss' of undefined
error during build:
TypeError: Cannot read property 'importedCss' of undefined
    at ManifestV2.getMetadataforChunk (node_modules/@samrum/vite-plugin-web-extension/dist/index.js:335:32)

the same error happens for V3

rendering chunks (10)...[webExtension] Cannot read property 'importedCss' of undefined
error during build:
TypeError: Cannot read property 'importedCss' of undefined
    at ManifestV3.getMetadataforChunk (node_modules/@samrum/vite-plugin-web-extension/dist/index.js:335:32)

Also when trying to use it directly in vite plugins i get the error:

Type 'Plugin' is not assignable to type 'PluginOption | PluginOption[]'.

"vite": "^2.8.6"

so i had to move it to rollup options

export default defineConfig({
	plugins: [
		react(),
	],
	build: {
		rollupOptions: {
			plugins: [
				webExtension({ manifest }),
			],
		},
	},
})

Popup & Options pages inlude inline JS from HMR violating CSP

I ran into an issue where the extension popup & options page could not be loaded by Chromium (110.0.5481.77) or Google Chrome (110.0.5481.177) with the following error message:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' http://localhost:5173". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.

Screenshot 2023-03-07 at 19 53 53

The output popup index.html file contains some inline JS from react-refresh (HMR?)

<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="module">
import RefreshRuntime from "http://localhost:5173/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>

    <script type="module" src="http://localhost:5173/@vite/client"></script>

    <meta charset="UTF-8" />
    <title>Popup</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="http://localhost:5173/src/entries/options/main.jsx"></script>
  </body>
</html>
manifest.json
{
  "author": "Roman Zipp",
  "description": "redacted",
  "name": "redacted",
  "version": "0.0.1",
  "content_scripts": [
    {
      "js": [
        "src/entries/contentScript/primary/main.js"
      ],
      "matches": [
        "redacted"
      ]
    },
    {
      "js": [
        "src/entries/contentScript/handshake/main.js"
      ],
      "matches": [
        "redacted"
      ]
    }
  ],
  "icons": {
    "16": "icons/16.png",
    "19": "icons/19.png",
    "32": "icons/32.png",
    "38": "icons/38.png",
    "48": "icons/48.png",
    "64": "icons/64.png",
    "96": "icons/96.png",
    "128": "icons/128.png",
    "256": "icons/256.png",
    "512": "icons/512.png"
  },
  "options_ui": {
    "page": "src/entries/options/index.html",
    "open_in_tab": true
  },
  "permissions": [
    "storage"
  ],
  "content_security_policy": {
    "extension_pages": "script-src 'self' http://localhost:5173; object-src 'self'"
  },
  "action": {
    "default_icon": {
      "16": "icons/16.png",
      "19": "icons/19.png",
      "32": "icons/32.png",
      "38": "icons/38.png"
    },
    "default_popup": "src/entries/popup/index.html"
  },
  "background": {
    "service_worker": "serviceWorker.js",
    "type": "module"
  },
  "host_permissions": [
    "redacted"
  ],
  "manifest_version": 3
}

According to the CSP docs there's no way to loosen the security policy in MV 2 & 3 for Chromium based browsers.

The @samrum/vite-plugin-web-extension package is up-to-date with version 3.1.1

I've generated the extension code base via your create-vite-plugin-web-extension utility for React.

Let me know if you need any further information. Thanks for your great work so far!

Can't use injected scripts

When I try to inject a script to the client (I need to use some globals available in the site) and build the extension, the transpiled code tries to use getUrl but that method is not available on the client (obviously)

There is any way to make this work?

Question - content script

Is there a way so that scripts injected via content script like this:

runtime.getURL('src/lib.ts')

const script = document.createElement('script')
script.type = 'module'
script.src = browser.runtime.getURL('src/lib.ts')

const head = document.head || document.getElementsByTagName('head')[0] || document.documentElement
head.appendChild(script)

are automatically added as well to web resources and paths resolved ?

also looks like if i include script in web_accessible_resources its not being handled, i cant find it in production build

	web_accessible_resources: [
		{
			matches: ['<all_urls>'],
			resources: ['src/lib.ts'],
		},
	],

how to use browser.storage.sync in ContentScript

Hi I'm trying to set the user preferences and saved them in storage. I would like to use storage.sync instead of storage.local.

But when I use browser.storage.sync.get, I get below error

Cannot read properties of undefined (reading 'sync')

How to solve this?

How to override browser start page

I'm novice and I wanna develop a chrome extension by Vue.My problem is: 'How to override my browser start page in this framework?'
I hope to override the default tab page, so I write these in the manifest:

... 
chrome_url_overrides : {
    newtab: "src/entries/tabs/index.html"
  },
...

And I make a directory under the /src, which named tabs. There are 3 files(index.html, App.vue, main.js) in it.
However, when I build the project, I found that the tab folder created before is not in the dist package. There are only popup, options, contentScript in the dist folder.

How to package tabs folder into the dist ? I'll appreicate that if anyone could give me a hand :)

Content Security Issues

I am getting the following error:

Screenshot 2022-09-14 at 12 13 52

when:

  • bootstrapping via npm init @samrum/vite-plugin-web-extension@latest an manifest V3
  • Loading the extension manually or using pnpm run serve:chrome

Additional Infos

  • OS: MacOS
  • Browser: Chrome Version 105.0.5195.102 (Official Build) (arm64) or Chrome Canary Version 107.0.5299.0 (Official Build) canary (arm64)
  • Package: @samrum/vite-plugin-web-extension: 2.0.0
  • Generated Manifest File:
{
  "author": "....",
  "description": "....",
  "name": "....",
  "version": "0.0.1",
  "manifest_version": 3,
  "action": {
    "default_icon": {
      "16": "icons/16.png",
      "19": "icons/19.png",
      "32": "icons/32.png",
      "38": "icons/38.png"
    },
    "default_popup": "src/entries/popup/index.html"
  },
  "background": {
    "service_worker": "serviceWorker.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "js": [
        "src/entries/contentScript/primary/main.js"
      ],
      "matches": [
        "*://*/*"
      ]
    }
  ],
  "host_permissions": [
    "*://*/*"
  ],
  "icons": {
    "16": "icons/16.png",
    "19": "icons/19.png",
    "32": "icons/32.png",
    "38": "icons/38.png",
    "48": "icons/48.png",
    "64": "icons/64.png",
    "96": "icons/96.png",
    "128": "icons/128.png",
    "256": "icons/256.png",
    "512": "icons/512.png"
  },
  "options_ui": {
    "page": "src/entries/options/index.html",
    "open_in_tab": true
  },
  "content_security_policy": {
    "extension_pages": "script-src 'self'  http://localhost:5173; object-src 'self'"
  }
}

Bundling a static page with the extension

Hey there!
Big thanks for this template. I've been on the hunt for one for a while, and this is the best one I found!

I have a question regarding bundling a static page with the extension:
On my background page I want to redirect the user to a static page that will come bundled with the extension. I've been trying to figure how to configure Vite to bundle the page alongside the manifest entry files, but couldn't figure it out.
Any clues on how make it happen?

The static page will use svelte, if it matters ๐Ÿค“

Error loading css modules in vite 4.4.8+

When I try to upgrade vite to a version higher than 4.4.7, after building the extension, the following messages appear in the console and cause the application to crash:

Error: Unable to preload CSS for /assets/index-1bdef0a0.css
    at HTMLLinkElement.<anonymous> (index-99ddc666.js:19:1496)

I'm using tailwind, but I have tried with a random css module and the same thing happens.

As usually, the extension works correctly in development mode.

Need synchronous content script import

I found the generated content script index:

(async () => { await import('https://localhost:../.../content.ts') })();

So my content script is injected asynchronously,
sometimes it's initialized after DOM although "run_at" prop is set to document_start.

Extension works only after reload in extension manager

Currently working on redirecting extension, but it seems to work only after reload in extension manager. When i close the browser the popup and contentScript seems to work but scripts from background not. Any clue why?

script replace is not work when using single quote

I have a project where the prettier plugin will replace all double quotes with single quotes

The following code is working

<script type='module' src="./main.ts"></script>

The following is the formatted code, and the script replacement will be invalid at this time

<script type='module' src='./main.ts'></script>

Clarify MV3-in-Firefox support in README

It wasn't entirely clear to me from the README whether MV3 in Firefox is supported. Trying it out it seems like no? Since the serve:firefox script did not pass --firefox-preview mv3 to web-ext, the manifest.ts lists a service_worker (not yet supported by Firefox), and even after fixing those two issues, I get "Error: Extension is invalid".

To set expectations, it would be nice if the README had an indication of the current status of support for MV3 in Firefox :)

Importing from within isolated function

Not exactly sure how to approach this issue. Service Worker needs to execute a script dynamically using chrome.scripting.executeScript with the func parameter, providing a function that will be serialized, sent across frames, and reconstructed on the other side. This creates a problem for importing anything from within the content script.

service-worker.ts

import ContentScript from './content-script';

chrome.runtime.onMessage.addListener((msg, sender) => {
  chrome.scripting.executeScript({
    target: {tabId:sender.tab.id},
    func: ContentScript,
    args: [secret],
  });
});

content-script.ts

import { say } from 'cowsay';  // this will never work anyway since it binds outside the scope of the function

export default async function(secret) {
  const { say } = await import('cowsay');  // this does not seem to work either

  console.log(say({text:secret});
}

manifest.name must NOT have more than 45 characters

What's happening?
I have the following manifest name which is 44 characters long "SwipeWell โ€” Screenshot & Swipe File Software", but when I run my extension locally, it throws an error complaining that it's more than 45 characters. I'd expect it to run correctly especially because I've manually edited the output manifest.json and the browser extension stores accept it as valid.

Expected Behavior
No error is thrown

Info
Using manifest v3
Using vite-plugin-web-extension: "^1.4.4"

Property 'name' does not exist on type 'EmittedPrebuiltChunk'

I forked the repo and I am not able to build or run; fails as such:

rollup v3.27.0
bundles src/index.ts โ†’ dist/index.cjs, dist/index.mjs...
(!) Plugin typescript: @rollup/plugin-typescript TS2339: Property 'name' does not exist on type 'EmittedFile'.
  Property 'name' does not exist on type 'EmittedPrebuiltChunk'.
src/index.ts: (69:49)

69         this.addWatchFile(file.fileName ?? file.name!);
                                                   ~~~~

created dist/index.cjs, dist/index.mjs in 660ms

[2023-07-30 17:55:27] waiting for changes...
^Cโ€‰ELIFECYCLEโ€‰ Command failed.

I tried to import EmittedPrebuiltChunk in src/index.ts but that resulted in more errors:

rollup v3.27.0
bundles src/index.ts โ†’ dist/index.cjs, dist/index.mjs...
(!) Plugin typescript: @rollup/plugin-typescript TS2339: Property 'concat' does not exist on type 'EmittedPrebuiltChunk | EmittedFile[]'.
  Property 'concat' does not exist on type 'EmittedPrebuiltChunk'.
src/index.ts: (61:29)

61       emitQueue = emitQueue.concat(emitFiles);
                               ~~~~~~

(!) Plugin typescript: @rollup/plugin-typescript TS2339: Property 'forEach' does not exist on type 'EmittedPrebuiltChunk | EmittedFile[]'.
  Property 'forEach' does not exist on type 'EmittedPrebuiltChunk'.
src/index.ts: (67:17)

67       emitQueue.forEach((file) => {
                   ~~~~~~~

(!) Plugin typescript: @rollup/plugin-typescript TS7006: Parameter 'file' implicitly has an 'any' type.
src/index.ts: (67:26)

67       emitQueue.forEach((file) => {
                            ~~~~

created dist/index.cjs, dist/index.mjs in 655ms

[2023-07-30 17:59:40] waiting for changes...

Am I missing something? ๐Ÿค”

Thanks for publishing this, it's perfect for my plans! Great work!

Content scripts don't work in dev

I'm getting the following message on Firefox when trying to use this plugin in dev:

WebExtension content scripts may only load modules with moz-extension URLs and not: โ€œhttp://localhost:5173/src/entries/contentScript/primary/main.tsโ€

Is it intended for the content script to get compiled into this dynamic import?

(async()=>{await import('http://localhost:5173/src/entries/contentScript/primary/main.ts')})();

Happy to provide any further details that might help.

Dynamic imports broken with 2.0.0

I updated version from 1.0.0 to 2.0.0
and my dynamic imports from javascript files are broken now

code:

import('./polyfills').then(() => /* ... */)

output:

BEFORE:

()=>import("./polyfills.js"),["assets/polyfills.js","assets/browser.js"])

AFTER:

()=>import((chrome!=null?chrome:browser).runtime.getURL("./polyfills.js")),[])

Where the build output for ./polyfills is assets/polyfills.js

License?

Would be helpful for projects that want to use as a dependency.

Make opening browser optional

I am using FreeBSD for development, and just spent two days to convert bundling from Rollup to Vite, because this plugin seemed promising for handling V3 manifest inconsistencies.

Only after I got that done, I realised that this plugin always opens a browser on dev mode using web-ext, which does not support FreeBSD. So PLEASE make this feature optional through config.

Bit tired of this...

HMR issues

Hi there!

I ran into some issues with the HMR functionality:
While watching for file changes and serving the extension on Chrome, the extension will reload itself entirely if a file has been changed, closing the pages I'm working on.
On Firefox it won't reload the extension entirely, but I will need to manually refresh the page to see the changes.

How to reproduce

  • Create a new project using npm init @samrum/vite-plugin-web-extension
  • Choose Manifest V2 + V3, Svelte and TypeScript
  • Run npm run watch and npm run serve:chrome parallelly
  • Open the extension options page in the browser and open the file /src/entries/options/App.svelte
  • Make changes in the file - the extension will reload itself entirely
  • You can also run npm run serve:firefox to see that HMR isn't working on Firefox

Any suggestion how to overcome this?

Thanks a lot for the work on this project! ๐Ÿ™

Error when running `npm run dev` on newly created extension project

I created a brand new project just now using command.
npm init @samrum/vite-plugin-web-extension@latest

Selected V2+3 manifest.
Selected React
Selected Typescript

When I run npm run dev i get an error.

  vite v2.9.5 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 329ms.

โœ˜ [ERROR] Could not resolve "/@vite/client"

    node_modules/@samrum/vite-plugin-web-extension/client.js:2:42:
      2 โ”‚   const { addStyleTarget } = await import("/@vite/client");
        โ•ต                                           ~~~~~~~~~~~~~~~

10:38:14 pm [vite] error while updating dependencies:
Error: Build failed with 1 error:
node_modules/@samrum/vite-plugin-web-extension/client.js:2:42: ERROR: Could not resolve "/@vite/client"
    at failureErrorWithLog (D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:1603:15)
    at D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:1249:28
    at runOnEndCallbacks (D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:1034:63)
    at buildResponseToResult (D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:1247:7)
    at D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:1356:14
    at D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:666:9
    at handleIncomingPacket (D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:763:9)
    at Socket.readFromStdout (D:\Dev\p\learn\webext1a\node_modules\esbuild\lib\main.js:632:7)
    at Socket.emit (node:events:526:28)
    at addChunk (node:internal/streams/readable:315:12)

I am on Windows 10, node v16.14.2, npm 8.5.0

After running npm run build both npm run serve:firefox and npm run serve:chrome seem to run fine.
When running serve:firefox running a npm run build will reload an updated extension into firefox.

I did notice the generated template code had typescript type errors, but that's a separate issue for later.

Fonts imported by css are not web accessible

When I import local font files using css they get compiled but they are not web accessible, as they are not referenced in the manifest, and I can't add them manually because of the hash that is added.

Rebuild doesn't always fire after saving a file

Hey!
For some reason, sometimes when I save a file that's part of the extension, it doesn't trigger a build in vite. The only workaround I've found is to then force save another file.
I think this happens only for files set as "additionalInputs". For example right now I have a file that's eventually imported from a file that's in that list, and it refuses to build. But if I go one level up, it for some reason works fine.
Is this known, or is there a fix for this?

How to deal with defines?

vite doesn't replace defines in dev. Resulting in errors such as ReferenceError: process is not defined.

Presumably, wherever those globals are defined - it can't be reached by e.g. content scripts.

Are you aware of a solution to this?

Cannot use MUI in content script because of prefix

after bundle, in <head>

<head>
/* ... */
<style data-emotion="css" data-s="">
    .css-lcrz3t-MuiTypography-root{margin:0;font-family:Noto Sans KR,sans-serif;font-weight:400;font-size:0.875rem;line-height:1.43;color:white;}
</style>

but in content script rendered component,

<span class="MuiTypography-root MuiTypography-body1 css-rfrue5-MuiTypography-root">Typo</span>

There's no css-lcrz3t- prefix so it could not render properly. How can I solve it? Or have I to wait until update?

import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS doesn't contain all CSS files referenced

I'm trying to load into shadow dom a simple page

<script lang="ts">
	import { Avatar } from '@skeletonlabs/skeleton';
</script>

<div>
	<h1>My New Web Extension</h1>
</div>
<Avatar src="https://i.pravatar.cc/" />
<style>
	:host {
		position: fixed;
		display: inline-block;
		top: 29px;
		right: 20px;
		z-index: 99999999;
	}
</style>

This requires to load Skeleton UI css, however in import.meta.PLUGIN_WEB_EXT_CHUNK_CSS_PATHS I only have 1 available CSS from my actual page, and the same in webAccessibleResources -> I don't have CSS files listed.

Is there any workaround for this?

Feature Request: `additionalInputs` paths outside of project root, globs, and additional options

First, thank you for the excellent project. I was looking an HMR solution for extension development, stumbled on this one, and have been very pleased with the results. Much appreciated.

Summary

Ideally, the additionalInputs option would:

  1. Handle files outside of the extension root directory
  2. Handle globs for files referenced in additionalInputs
  3. Support options similar to copy-related plugins like rollup-plugin-copy or vite-plugin-static-copy (which is based on the former plugin) to set the file destination within /dist, flatten directories, rename files, etc.

Use Case

I have a monorepo containing an extension generated by this project stored in an /extension directory. The extension requires files accessed via the parent directory. For example, I'd like to include files from /files directory in the directory structure below:

.
โ”œโ”€โ”€ extension
โ”‚   โ”œโ”€โ”€ src
โ”‚   โ”‚   โ”œโ”€โ”€ assets
โ”‚   โ”‚   โ”œโ”€โ”€ entries
โ”‚   โ”‚   โ””โ”€โ”€ manifest.js
โ”‚   โ”œโ”€โ”€ package.json
โ”‚   โ””โ”€โ”€ vite.config.js
โ”œโ”€โ”€ files
โ”‚   โ”œโ”€โ”€ file1.js
โ”‚   โ”œโ”€โ”€ file2.js
โ”‚   โ”œโ”€โ”€ file3.js
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ package.json

I hoped I could use the additionalInputs option to watch/copy these files to /dist, add them to the web_accessible_resources section in manifest.json automatically, and have that work for both a build and development using HMR:

webExtension({
    manifest: getManifest(),
    additionalInputs: {
        scripts: ['../files/file1.js'],
    },
})

Unfortunately, using paths outside of the extension root results in the following error:

Invalid substitution "../files/file" for placeholder "[name]" in "output.entryFileNames" pattern, can be neither absolute nor relative path.

I've tried several other approaches to using files outside of the extension root, but they all have issues:

  • File are copied but not automatically referenced in under web_accessible_resources in manifest.json.
  • Files are copied but not watched
  • Files are copied but not detected by webExtension plugin hook (copy happens after webExtension)
  • Files copied to /extension/src create unwanted duplicates outside of /dist directory
  • File copying and manifest.json updates work properly when I build but not during development with HMR.

I can cobble a solution together, but it will be a much less elegant since the additionalInputs option does exactly what I need except it currently does not work with files outside of the extension root.

Proposed Enhancements

Given the same directory structure shown above, I'd like to use a glob pattern to reference all of the files in the /files directory:

webExtension({
  manifest: getManifest(),
  additionalInputs: {
    scripts: ['../files/*.js'],
  },
});

This would expect this to produce the following:

.
โ””โ”€โ”€ extension
    โ””โ”€โ”€ dist
        โ”œโ”€โ”€ ...
        โ”œโ”€โ”€ manifest.json
        โ””โ”€โ”€ files
            โ”œโ”€โ”€ file1.js
            โ”œโ”€โ”€ file2.js
            โ””โ”€โ”€ file3.js
"web_accessible_resources": [
  {
    "resources": ["files/*.js"],
    "matches": ["*://*/*"],
  },
],

If options similar to the ones found in rollup-plugin-copy or vite-plugin-static-copy were added, the additionalInputs configuration could look something like this:

webExtension({
  manifest: getManifest(),
  additionalInputs: {
    scripts: [
      {
        src: '../files/*.js',
        dest: 'foo/bar',
        flatten: true,
        rename: (name, extension, fullPath) => `renamed-${name}.${extension}`
      },
    ],
  },
});
.
โ””โ”€โ”€ extension
    โ””โ”€โ”€ dist
        โ”œโ”€โ”€ ...
        โ”œโ”€โ”€ manifest.json
        โ””โ”€โ”€ foo
            โ””โ”€โ”€ bar
                โ”œโ”€โ”€ renamed-file1.js
                โ”œโ”€โ”€ renamed-file2.js
                โ””โ”€โ”€ renamed-file3.js

Thanks in advance for your consideration. I'm happy discuss in more detail and contribute to the effort if you believe this would be worth doing.

Dynamic content scripts and popups

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).

HMR Solution Not Working in Manifest V3

This HMR solution for local development does not work in Manifest V3:

(async()=>{await import('http://localhost:3000/src/contentScript.ts')})();

For example, Chrome reports:

Refused to load the script 'http://localhost:3000/src/contentScript.ts' because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Npm run build error: MV2&3, Vue, TS

npm init @samrum/vite-plugin-web-extension@latest vite-web-extension

โˆš Manifest version: ยป Manifest V2 & V3
โˆš Framework: ยป Vue
โˆš Add TypeScript? ...  Yes

npn install

npm WARN deprecated @volar/[email protected]: WARNING: This project has been renamed to @johnsoncodehk/html2pug. Install using @johnsoncodehk/html2pug instead.
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated [email protected]: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated [email protected]: WARNING: This project has been renamed to @volar/pug-language-service. Install using @volar/pug-language-service instead.
npm WARN deprecated [email protected]: WARNING: This project has been renamed to @volar/typescript-language-service. Install using @volar/typescript-language-service instead.
npm WARN deprecated [email protected]: WARNING: This project has been renamed to @volar/vue-language-service. Install using @volar/vue-language-service instead.
npm WARN deprecated [email protected]: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.

added 680 packages, and audited 681 packages in 2m

73 packages are looking for funding
  run `npm fund` for details

10 vulnerabilities (9 moderate, 1 high)

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

npm run build

> [email protected] build
> vue-tsc --noEmit && vite build

src/components/PageContent.vue:2:14 - error TS2304: Cannot find name 'imageUrl'.

2   <img :src="imageUrl" height="45" alt="" />
               ~~~~~~~~

src/entries/contentScript/primary/App.vue:3:16 - error TS2304: Cannot find name 'logoImageUrl'.

3     <img :src="logoImageUrl" height="50" alt="" />
                 ~~~~~~~~~~~~

src/components/PageContent.vue:6:33 - error TS2304: Cannot find name 'increment'.

6   <button type="button" @click="increment">Clicks: {{ count }}</button>
                                  ~~~~~~~~~

src/components/PageContent.vue:6:55 - error TS2304: Cannot find name 'count'.

6   <button type="button" @click="increment">Clicks: {{ count }}</button>
                                                        ~~~~~

node_modules/@vue/runtime-core/dist/runtime-core.d.ts:1846:33 - error TS2344: Type 'HostElement' does not satisfy the constraint 'RendererElement'.
1846     transition: TransitionHooks<HostElement> | null;
                                     ~~~~~~~~~~~

  node_modules/@vue/runtime-core/dist/runtime-core.d.ts:1828:57
    1828 export declare interface VNode<HostNode = RendererNode, HostElement = RendererElement, ExtraProps = {
                                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    This type parameter might need an `extends RendererElement` constraint.


Found 5 errors in 3 files.

Errors  Files
     3  src/components/PageContent.vue:2
     1  src/entries/contentScript/primary/App.vue:3
     1  node_modules/@vue/runtime-core/dist/runtime-core.d.ts:1846

rollup types conflict with vite types

> tsc
node_modules/@samrum/vite-plugin-web-extension/types/index.d.ts:40:5 - error TS2687: All declarations of 'viteMetadata' must have identical modifiers.

40     viteMetadata: ChunkMetadata;
       ~~~~~~~~~~~~

node_modules/@samrum/vite-plugin-web-extension/types/index.d.ts:40:5 - error TS2717: Subsequent property declarations must have the same type.  Property 'viteMetadata' must be of type 'ChunkMetadata | undefined', but here has type 'ChunkMetadata'.

40     viteMetadata: ChunkMetadata;
       ~~~~~~~~~~~~

  node_modules/vite/types/metadata.d.ts:8:5
    8     viteMetadata?: ChunkMetadata
          ~~~~~~~~~~~~
    'viteMetadata' was also declared here.

node_modules/vite/types/metadata.d.ts:8:5 - error TS2687: All declarations of 'viteMetadata' must have identical modifiers.

8     viteMetadata?: ChunkMetadata
      ~~~~~~~~~~~~


Found 3 errors in 2 files.

Question: injection to MAIN environment

For the extension that I'm building, I'm trying to inject to the MAIN environment

chrome.scripting.registerContentScripts([
  {
    id: 'inpage',
    matches: ['http://*/*', 'https://*/*'],
    js: [
      'src/entries/content-scripts/inject.js'
    ],
    runAt: 'document_start',
    world: 'MAIN'
  },
]);

but generated code
(async()=>{await import(chrome.runtime.getURL("assets/src/entries/content-scripts/inject.8e490e7d.js"))})();

raise an error chrome.runtime.getURL is not a function in console

I understand that neither the file (inject.8e490e7d.js) nor the method (chrome.runtime.getURL) exists in MAIN context, but how to inject correctly?

Question: How to compile `content_scripts.css`?

I have this in my config

    content_scripts: [
      {
        matches: ['https://*.youtube.com/*'],
        js: ['src/inject.js'],
        css: ['src/inject.css'],
      },
    ],

And I have a tailwind css file

/* inject.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

I expect the inject.css to be compiled but it is only copy pasted into dist/src. Is this possible? I'm new to vite and web extensions.

Question: how to run icons through loaders?

For the extension that I'm building, I've got one core icon which is an SVG, and I was hoping to use vite-imagetools to process that into a bunch of different sized PNGs at build time, but I couldn't tell how to get the two vite plugins to work together.

Is it possible to do something like #52 for the icons as well?

Feat Request: Set hash route in manifest entry file.

I really hope that hash route can be set in the manifest entry file.
It is very useful in the following usage scenarios:

{
  // ...
  "action": {
    "default_popup": "src/app.html#/popup/dashboard"
  },
  "options_page": "src/app.html#/options",
  // ...
}

Multiple pages are in App, it can reduce my template code.

How do you hot reload the extension?

I am aware of pnpm run build and pnpm run serve:firefox for running the build but is it possible to run the extension in a state where changes in the source will automatically be updated into the browser?

Version 4.x does not support scss

Hi, when I try to use .scss in the manifest in v4.x (tried in both 4.0.0 and 4.1.0) the compiler shows an error: rendering chunks (4)...[webExtension] Failed to find chunk info for src/entries/contentScript/primary/main.tsx

Also, when I try to use both tailwind and scss at the same time in v3.1.1 only the css from tailwind is added in the output manifest, but all css files are bundled correctly inside output folder.

vite version: 4.2.1

As always it works fine in dev mode.

Snippet:

const sharedManifest = {
  content_scripts: [
    {
      js: ['src/entries/contentScript/primary/main.tsx'],
      css: ['src/entries/contentScript/primary/main.scss'],
      matches: ['https://*.example.net/*', 'https://*.example.com/*'],
      run_at: 'document_start',
      all_frames: true
    }
  ],
  icons: {
    16: 'icons/icon.png',
    48: 'icons/icon.png',
    128: 'icons/icon.png'
  },
  permissions: ['webNavigation', 'storage'] as chrome.runtime.ManifestPermissions[]
}

Question: how to enable vue devtools?

Hi,

Thanks for the great plugin, is working great!
I do however have a question. I'm creating a chrome extension with Vue but I would like to be able to debug a litter better by using the vue-devtools chrome extension. But I can't manage setting this up for some reason.

Any idea what I would be doing wrong?

I've changed my vite.config so it looks like this but that didn't help.

import VueDevTools from 'vite-plugin-vue-devtools'
import { defineConfig } from "vite"
import { getManifest } from "./src/manifest"
import path from "path"
import vue from "@vitejs/plugin-vue"
import webExtension from "@samrum/vite-plugin-web-extension"

// https://vitejs.dev/config/
export default defineConfig(() => {
  return {
    plugins: [
      VueDevTools(),
      vue(),
      webExtension({
        manifest: getManifest(),
      }),
    ],
    resolve: {
      alias: {
        "~": path.resolve(__dirname, "./src"),
      },
    },
  };
});

Build not emitting scripts included in web_accessible_resources

For example, input Manifest V3:

{
  "web_accessible_resources": [
    {
      "resources": ["src/inpage.ts"],
      "matches": ["file://*/*", "http://*/*", "https://*/*"]
    }
  ]
}

Makes it into the output manifest.json, but the transpiled version of src/inpage.ts => src/inpage.{HASH}.js is never emitted in the output (and the path in the manifest still points to the typescript file).

Add permissions

Hey,
i tried to add new permissions to the manifest:

...
"version": "1.0.10",
"manifest_version": 3,
"permissions": [
  "storage"
],

Any suggestions why chrome doesnt recognize?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.