feat!: Add type safety to browser.runtime.getURL
Entrypoint outputs and files in the public directory are typed properly. Chunks and other generated files are not typed since they aren't often accessed at runtime. BREAKING CHANGE: `browser` is now imported from `wxt/browser` instead of `webextension-polyfill`
This commit is contained in:
@@ -7,4 +7,9 @@ export default defineBackground(() => {
|
||||
firefox: __IS_FIREFOX__,
|
||||
manifestVersion: __MANIFEST_VERSION__,
|
||||
});
|
||||
|
||||
// @ts-expect-error: should only accept entrypoints or public assets
|
||||
browser.runtime.getURL('/');
|
||||
browser.runtime.getURL('/background.js');
|
||||
browser.runtime.getURL('/icon/128.png');
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ To setup your test environment for auto-imports, see [Testing](/get-started/test
|
||||
|
||||
Some WXT APIs can be used without importing them:
|
||||
|
||||
- [`browser`](/config.md#browser) from `webextension-polyfill`
|
||||
- [`browser`](/config.md#browser) from `wxt/browser`, a small wrapper around `webextension-polyfill`
|
||||
- [`defineContentScript`](/config.md#defiencontentscript) from `wxt/client`
|
||||
- [`defineBackground`](/config.md#definebackgroundscript) from `wxt/client`
|
||||
|
||||
|
||||
@@ -21,10 +21,17 @@ describe('Auto Imports', () => {
|
||||
".wxt/types/paths.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
type EntrypointPath =
|
||||
| \\"/background.js\\"
|
||||
| \\"/content-scripts/overlay.js\\"
|
||||
| \\"/popup.html\\"
|
||||
import \\"wxt/browser\\";
|
||||
|
||||
declare module \\"wxt/browser\\" {
|
||||
type PublicPath =
|
||||
| \\"/background.js\\"
|
||||
| \\"/content-scripts/overlay.js\\"
|
||||
| \\"/popup.html\\"
|
||||
export interface ProjectRuntime extends Runtime.Static {
|
||||
getURL(path: PublicPath): string;
|
||||
}
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -37,18 +44,18 @@ describe('Auto Imports', () => {
|
||||
|
||||
expect(await project.serializeFile('.wxt/types/imports.d.ts'))
|
||||
.toMatchInlineSnapshot(`
|
||||
".wxt/types/imports.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
export {}
|
||||
declare global {
|
||||
const browser: typeof import('webextension-polyfill')
|
||||
const defineBackground: typeof import('wxt/client')['defineBackground']
|
||||
const defineConfig: typeof import('wxt')['defineConfig']
|
||||
const defineContentScript: typeof import('wxt/client')['defineContentScript']
|
||||
const mountContentScriptUi: typeof import('wxt/client')['mountContentScriptUi']
|
||||
}
|
||||
"
|
||||
`);
|
||||
".wxt/types/imports.d.ts
|
||||
----------------------------------------
|
||||
// Generated by wxt
|
||||
export {}
|
||||
declare global {
|
||||
const browser: typeof import('wxt/browser')['browser']
|
||||
const defineBackground: typeof import('wxt/client')['defineBackground']
|
||||
const defineConfig: typeof import('wxt')['defineConfig']
|
||||
const defineContentScript: typeof import('wxt/client')['defineContentScript']
|
||||
const mountContentScriptUi: typeof import('wxt/client')['mountContentScriptUi']
|
||||
}
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
+4
-1
@@ -39,9 +39,12 @@
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./client": {
|
||||
"require": "./dist/client.cjs",
|
||||
"import": "./dist/client.js",
|
||||
"types": "./dist/client.d.ts"
|
||||
},
|
||||
"./browser": {
|
||||
"import": "./dist/browser.js",
|
||||
"types": "./dist/browser.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -37,6 +37,13 @@ await Promise.all([
|
||||
dts: true,
|
||||
silent: true,
|
||||
}),
|
||||
tsup.build({
|
||||
entry: { browser: 'src/client/browser.ts' },
|
||||
format: ['esm'],
|
||||
sourcemap: 'inline',
|
||||
dts: true,
|
||||
silent: true,
|
||||
}),
|
||||
...virtualEntrypoints.map((entryName) =>
|
||||
tsup.build({
|
||||
entry: {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import originalBrowser, { Browser, Runtime } from 'webextension-polyfill';
|
||||
|
||||
export interface AugmentedBrowser extends Browser {
|
||||
runtime: ProjectRuntime;
|
||||
}
|
||||
|
||||
export interface ProjectRuntime extends Runtime.Static {
|
||||
// Overriden per-project
|
||||
}
|
||||
|
||||
export const browser: AugmentedBrowser = originalBrowser;
|
||||
@@ -9,9 +9,9 @@ import {
|
||||
import * as plugins from '../vite-plugins';
|
||||
import { removeEmptyDirs } from '../utils/removeEmptyDirs';
|
||||
import { getEntrypointBundlePath } from '../utils/entrypoints';
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPublicFiles } from '../utils/public';
|
||||
|
||||
export async function buildEntrypoints(
|
||||
groups: EntrypointGroup[],
|
||||
@@ -132,11 +132,10 @@ function getBuildOutputChunks(
|
||||
async function copyPublicDirectory(
|
||||
config: InternalConfig,
|
||||
): Promise<BuildOutput['publicAssets']> {
|
||||
const files = await getPublicFiles(config);
|
||||
if (files.length === 0) return [];
|
||||
|
||||
const publicAssets: BuildOutput['publicAssets'] = [];
|
||||
if (!(await fs.exists(config.publicDir))) return publicAssets;
|
||||
|
||||
const files = await glob('**/*', { cwd: config.publicDir });
|
||||
|
||||
for (const file of files) {
|
||||
const srcPath = resolve(config.publicDir, file);
|
||||
const outPath = resolve(config.outDir, file);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { relative, resolve } from 'path';
|
||||
import { getEntrypointBundlePath } from '../utils/entrypoints';
|
||||
import { getUnimportOptions } from '../utils/auto-imports';
|
||||
import { getGlobals } from '../utils/globals';
|
||||
import { getPublicFiles } from '../utils/public';
|
||||
|
||||
/**
|
||||
* Generate and write all the files inside the `InternalConfig.typesDir` directory.
|
||||
@@ -49,23 +50,33 @@ async function writePathsDeclarationFile(
|
||||
): Promise<string> {
|
||||
const filePath = resolve(config.typesDir, 'paths.d.ts');
|
||||
const unions = entrypoints
|
||||
.map((entry) => {
|
||||
const path = getEntrypointBundlePath(
|
||||
.map((entry) =>
|
||||
getEntrypointBundlePath(
|
||||
entry,
|
||||
config.outDir,
|
||||
entry.inputPath.endsWith('.html') ? '.html' : '.js',
|
||||
);
|
||||
return ` | "/${path}"`;
|
||||
})
|
||||
.sort();
|
||||
),
|
||||
)
|
||||
.concat(await getPublicFiles(config))
|
||||
.map((path) => ` | "/${path}"`)
|
||||
.sort()
|
||||
.join('\n');
|
||||
|
||||
const template = `// Generated by wxt
|
||||
import "wxt/browser";
|
||||
|
||||
declare module "wxt/browser" {
|
||||
type PublicPath =
|
||||
{{ union }}
|
||||
export interface ProjectRuntime extends Runtime.Static {
|
||||
getURL(path: PublicPath): string;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
await fs.writeFile(
|
||||
filePath,
|
||||
[
|
||||
'// Generated by wxt',
|
||||
'type EntrypointPath =',
|
||||
...(unions.length === 0 ? [' never'] : unions),
|
||||
].join('\n') + '\n',
|
||||
template.replace('{{ union }}', unions || ' | never'),
|
||||
);
|
||||
|
||||
return filePath;
|
||||
|
||||
@@ -7,11 +7,8 @@ export function getUnimportOptions(
|
||||
): Partial<UnimportOptions> {
|
||||
const defaultOptions: Partial<UnimportOptions> = {
|
||||
debugLog: config.logger.debug,
|
||||
imports: [
|
||||
{ name: '*', as: 'browser', from: 'webextension-polyfill' },
|
||||
{ name: 'defineConfig', from: 'wxt' },
|
||||
],
|
||||
presets: [{ package: 'wxt/client' }],
|
||||
imports: [{ name: 'defineConfig', from: 'wxt' }],
|
||||
presets: [{ package: 'wxt/client' }, { package: 'wxt/browser' }],
|
||||
warn: config.logger.warn,
|
||||
dirs: ['./components/*', './composables/*', './hooks/*', './utils/*'],
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@ export async function importTsFile<T>(
|
||||
const unimport = createUnimport({
|
||||
...getUnimportOptions(config),
|
||||
// Only allow specific imports, not all from the project
|
||||
imports: [{ name: '*', as: 'browser', from: 'webextension-polyfill' }],
|
||||
dirs: [],
|
||||
});
|
||||
await unimport.init();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { InternalConfig } from '../types';
|
||||
import fs from 'fs-extra';
|
||||
import glob from 'fast-glob';
|
||||
|
||||
/**
|
||||
* Get all the files in the project's public directory. Returned paths are relative to the
|
||||
* `config.publicDir`.
|
||||
*/
|
||||
export async function getPublicFiles(
|
||||
config: InternalConfig,
|
||||
): Promise<string[]> {
|
||||
if (!(await fs.exists(config.publicDir))) return [];
|
||||
|
||||
return await glob('**/*', { cwd: config.publicDir });
|
||||
}
|
||||
Reference in New Issue
Block a user