Skip to content

fix(types): Add type safety to browser.runtime.executeScript files option#2142

Merged
aklinker1 merged 6 commits intowxt-dev:mainfrom
nickbar01234:nickbar01234/typed-execute-script
Feb 21, 2026
Merged

fix(types): Add type safety to browser.runtime.executeScript files option#2142
aklinker1 merged 6 commits intowxt-dev:mainfrom
nickbar01234:nickbar01234/typed-execute-script

Conversation

@nickbar01234
Copy link
Contributor

@nickbar01234 nickbar01234 commented Feb 21, 2026

Overview

Previously, browser.script.executeScript({ files: [] }) does not provide any type hint about what file paths are injectable. This PR augments the type definition to all ScriptPublicPath, generated from path.d.ts.

A couple of notes

  • I think ScriptPublicPath union is wider than what can be provided to executeScript. The worst case is we run into runtime errors, which is similar to the state before this change.
  • This is a breaking change for downstream consumers that specifies a non-existent scripts

Manual Testing

  • Manual link with a local extension to confirm the build generates the right type definition

Related Issue

This PR closes #<issue_number>

@netlify
Copy link

netlify bot commented Feb 21, 2026

Deploy Preview for creative-fairy-df92c4 ready!

Name Link
🔨 Latest commit e2293ad
🔍 Latest deploy log https://app.netlify.com/projects/creative-fairy-df92c4/deploys/699a1a939cc20c0008cf9b7e
😎 Deploy Preview https://deploy-preview-2142--creative-fairy-df92c4.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

WxtBrowser["scripting"]["executeScript"]
>[0] & { files: Array<${paths.join(' | ')}>; func?: never | undefined };

type ReplaceFirstArg<Fnc, Arg> = Fnc extends {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tbh: Not a big fan of this typescript magic, so open to other suggestions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, @aklinker1 curious if it's an option to update the backing @wxt-dev/browser package to have a generic T for file paths, which we can dynamically declare when generating the declaration file based on the filenames

Copy link
Member

@aklinker1 aklinker1 Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nickbar01234 Did you try how we do it here?

const template = `// Generated by wxt
import "wxt/browser";
declare module "wxt/browser" {
export type PublicPath =
{{ union }}
type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`>
export interface WxtRuntime {
getURL(path: PublicPath): string;
getURL(path: \`\${HtmlPublicPath}\${string}\`): string;
}
}
`;
return {
path: 'types/paths.d.ts',
text: template.replace('{{ union }}', unions || ' | never'),
tsReference: true,
};
}

/**
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
* - `.wxt/types/paths.d.ts`
*/
export interface WxtRuntime {}
/**
* This interface is empty because it is generated per-project when running `wxt prepare`. See:
* - `.wxt/types/i18n.d.ts`
*/
export interface WxtI18n {}
export type WxtBrowser = Omit<typeof _browser, 'runtime' | 'i18n'> & {
runtime: WxtRuntime & Omit<(typeof _browser)['runtime'], 'getURL'>;
i18n: WxtI18n & Omit<(typeof _browser)['i18n'], 'getMessage'>;
};
export const browser: WxtBrowser = _browser;

We should be able to simplify this quite a bit. Unless there's something different about what you're doing here.

Copy link
Member

@aklinker1 aklinker1 Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's an option to update the backing @wxt-dev/browser package to have a generic T for file paths, which we can dynamically declare when generating the declaration file based on the filenames

I'm hesitant to make excessive changes to that package since it's generated based on @types/chrome. It may be possible to automate, but it would become more fragile. I'd prefer we use the existing patterns for augmenting the browser types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I think I see what wxt/packages/wxt/src/browser.ts is doing. Let me try refactoring this code to match the pattern

type ScriptInjection<Args extends any[], Result> =
Browser.scripting.ScriptInjection<Args, Result> extends infer T
? T extends { files: string[] }
? Omit<T, 'files'> & { files: ScriptPublicPath[] }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API docs say that file injections can be either CSS or JS, which does not align with my understanding of executeScript API.

The path of the JS or CSS files to inject, relative to the extension's root directory. Exactly one of the files or func must be specified.

If this is the case, we'd need to add more union to ScriptPublicPath. The other concern is downstream consumers with wrong file name will break when they pick up this version. My thought is that it's ok for better type safety

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it should be fine for now.

MDN doesn't include CSS files in their docs: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript

I'd say it's fine for now, if someone opens an issue we'll know what needs added.

@nickbar01234
Copy link
Contributor Author

@aklinker1 just updated the implementation whenever you have some downtime to review!

@aklinker1 aklinker1 changed the title Augment executeScript type fix(types): Add type safety to browser.runtime.executeScript files option. Feb 21, 2026
Copy link
Member

@aklinker1 aklinker1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks. Much simpler!

Comment on lines 36 to 47
interface WxtScripting {
executeScript: {
<Args extends any[], Result>(
injection: ScriptInjection<Args, Result>,
): Promise<InjectionResult<Result>>;

<Args extends any[], Result>(
injection: ScriptInjection<Args, Result>,
callback: (results: InjectionResult<Result>) => void,
): void;
};
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I guess we don't need to generate some of the overrides per-project, like the runtime overrides... I could just import the PublicPath type like you did here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not too familiar with the typescript build process itself, so I was mostly referencing https://github.com/wxt-dev/wxt/blob/main/packages/wxt/src/utils/inject-script.ts#L4-L8 that also uses the public type before they existed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I'll move to this when possible. Modules like i18n will still need to generate it's own module augmentations, but the static stuff can be put here.

@aklinker1 aklinker1 changed the title fix(types): Add type safety to browser.runtime.executeScript files option. fix(types): Add type safety to browser.runtime.executeScript files option Feb 21, 2026
@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 21, 2026

Open in StackBlitz

@wxt-dev/analytics

npm i https://pkg.pr.new/@wxt-dev/analytics@2142

@wxt-dev/auto-icons

npm i https://pkg.pr.new/@wxt-dev/auto-icons@2142

@wxt-dev/browser

npm i https://pkg.pr.new/@wxt-dev/browser@2142

@wxt-dev/i18n

npm i https://pkg.pr.new/@wxt-dev/i18n@2142

@wxt-dev/module-react

npm i https://pkg.pr.new/@wxt-dev/module-react@2142

@wxt-dev/module-solid

npm i https://pkg.pr.new/@wxt-dev/module-solid@2142

@wxt-dev/module-svelte

npm i https://pkg.pr.new/@wxt-dev/module-svelte@2142

@wxt-dev/module-vue

npm i https://pkg.pr.new/@wxt-dev/module-vue@2142

@wxt-dev/runner

npm i https://pkg.pr.new/@wxt-dev/runner@2142

@wxt-dev/storage

npm i https://pkg.pr.new/@wxt-dev/storage@2142

@wxt-dev/unocss

npm i https://pkg.pr.new/@wxt-dev/unocss@2142

@wxt-dev/webextension-polyfill

npm i https://pkg.pr.new/@wxt-dev/webextension-polyfill@2142

wxt

npm i https://pkg.pr.new/wxt@2142

commit: e2293ad

@codecov
Copy link

codecov bot commented Feb 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.14%. Comparing base (3b15450) to head (e2293ad).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2142      +/-   ##
==========================================
+ Coverage   75.94%   76.14%   +0.19%     
==========================================
  Files         113      113              
  Lines        3043     3043              
  Branches      680      680              
==========================================
+ Hits         2311     2317       +6     
+ Misses        646      641       -5     
+ Partials       86       85       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aklinker1 aklinker1 enabled auto-merge (squash) February 21, 2026 20:57
@nickbar01234
Copy link
Contributor Author

btw @aklinker1, I notice that we don't collect CSS files in public path and the scripts eligible for injectScripts should be declared web_accessible_resources right? Is it worth doing a cleanup here too?

@aklinker1 aklinker1 merged commit 42d65cb into wxt-dev:main Feb 21, 2026
18 checks passed
@nickbar01234 nickbar01234 deleted the nickbar01234/typed-execute-script branch February 21, 2026 20:58
@github-actions
Copy link
Contributor

Thanks for helping make WXT better!

@aklinker1
Copy link
Member

scripts eligible for injectScripts should be declared web_accessible_resources right? Is it worth doing a cleanup here too?

@nickbar01234 I'm against making assumptions to generate web_accessible_resources. For unlisted scripts, they can also be used as web workers, which don't always need to be added to web_accessible_resources.

That said, I could see a feature like this being useful. Feel free to implement if you'd like: #536 (comment)

@nickbar01234
Copy link
Contributor Author

scripts eligible for injectScripts should be declared web_accessible_resources right? Is it worth doing a cleanup here too?

@nickbar01234 I'm against making assumptions to generate web_accessible_resources. For unlisted scripts, they can also be used as web workers, which don't always need to be added to web_accessible_resources.

That said, I could see a feature like this being useful. Feel free to implement if you'd like: #536 (comment)

gotcha. Is anyone actively working on #544? The issue you referenced seems like it can be bundled as part of the issue

@aklinker1
Copy link
Member

Actually, nevermind. After looking at #544 again, I don't think I want to add these features. It might seem convenient at first, but managing all these global options (permissions, web accessible resources) in one place keeps things simple. If they're spread all over the place, you have to look everywhere for them - imagine removing a permission from one entrypoint, forgetting to remove it from another. Seems like it will cause bugs and tech debt... I think having them in one place is the right move.

And thinking about it more, a accessible: true option wouldn't be enough to build a MV3 web accessible resources entry.

So I'm actually gonna close #544 as not planned, sorry about that.

aklinker1 added a commit that referenced this pull request Feb 22, 2026
…` option (#2142)

Co-authored-by: nickbar01234 <nickbar01234gmail.com>
Co-authored-by: Aaron <aaronklinker1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants