diff --git a/docs/guide/essentials/target-different-browsers.md b/docs/guide/essentials/target-different-browsers.md index 5db2fda5..c854a976 100644 --- a/docs/guide/essentials/target-different-browsers.md +++ b/docs/guide/essentials/target-different-browsers.md @@ -75,3 +75,5 @@ Here are some examples: ``` + +Alternatively, you can use the [`filterEntrypoints` config](/api/reference/wxt/interfaces/InlineConfig#filterentrypoints) to list all the entrypoints you want to build. diff --git a/packages/wxt/src/core/utils/building/__tests__/find-entrypoints.test.ts b/packages/wxt/src/core/utils/building/__tests__/find-entrypoints.test.ts index 6fd492a1..566a9c61 100644 --- a/packages/wxt/src/core/utils/building/__tests__/find-entrypoints.test.ts +++ b/packages/wxt/src/core/utils/building/__tests__/find-entrypoints.test.ts @@ -701,7 +701,7 @@ describe('findEntrypoints', () => { }); describe('include option', () => { - it("should filter out the background when include doesn't contain the target browser", async () => { + it("should mark the background as skipped when include doesn't contain the target browser", async () => { globMock.mockResolvedValueOnce(['background.ts']); importEntrypointMock.mockResolvedValue({ include: ['not' + config.browser], @@ -709,10 +709,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'background', + skipped: true, + }), + ]); }); - it("should filter out content scripts when include doesn't contain the target browser", async () => { + it("should mark content scripts as skipped when include doesn't contain the target browser", async () => { globMock.mockResolvedValueOnce(['example.content.ts']); importEntrypointMock.mockResolvedValue({ include: ['not' + config.browser], @@ -720,10 +725,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'example', + skipped: true, + }), + ]); }); - it("should filter out the popup when include doesn't contain the target browser", async () => { + it("should mark the popup as skipped when include doesn't contain the target browser", async () => { globMock.mockResolvedValueOnce(['popup.html']); readFileMock.mockResolvedValueOnce( ` @@ -737,10 +747,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'popup', + skipped: true, + }), + ]); }); - it("should filter out the options page when include doesn't contain the target browser", async () => { + it("should mark the options page as skipped when include doesn't contain the target browser", async () => { globMock.mockResolvedValueOnce(['options.html']); readFileMock.mockResolvedValueOnce( ` @@ -754,10 +769,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'options', + skipped: true, + }), + ]); }); - it("should filter out an unlisted page when include doesn't contain the target browser", async () => { + it("should mark unlisted pages as skipped when include doesn't contain the target browser", async () => { globMock.mockResolvedValueOnce(['unlisted.html']); readFileMock.mockResolvedValueOnce( ` @@ -771,12 +791,17 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'unlisted', + skipped: true, + }), + ]); }); }); describe('exclude option', () => { - it('should filter out the background when exclude contains the target browser', async () => { + it('should mark the background as skipped when exclude contains the target browser', async () => { globMock.mockResolvedValueOnce(['background.ts']); importEntrypointMock.mockResolvedValue({ exclude: [config.browser], @@ -784,10 +809,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'background', + skipped: true, + }), + ]); }); - it('should filter out content scripts when exclude contains the target browser', async () => { + it('should mark content scripts as skipped when exclude contains the target browser', async () => { globMock.mockResolvedValueOnce(['example.content.ts']); importEntrypointMock.mockResolvedValue({ exclude: [config.browser], @@ -795,10 +825,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'example', + skipped: true, + }), + ]); }); - it('should filter out the popup when exclude contains the target browser', async () => { + it('should mark the popup as skipped when exclude contains the target browser', async () => { globMock.mockResolvedValueOnce(['popup.html']); readFileMock.mockResolvedValueOnce( ` @@ -810,10 +845,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'popup', + skipped: true, + }), + ]); }); - it('should filter out the options page when exclude contains the target browser', async () => { + it('should mark the options page as skipped when exclude contains the target browser', async () => { globMock.mockResolvedValueOnce(['options.html']); readFileMock.mockResolvedValueOnce( ` @@ -825,10 +865,15 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'options', + skipped: true, + }), + ]); }); - it('should filter out an unlisted page when exclude contains the target browser', async () => { + it('should mark unlisted pages as skipped when exclude contains the target browser', async () => { globMock.mockResolvedValueOnce(['unlisted.html']); readFileMock.mockResolvedValueOnce( ` @@ -840,12 +885,17 @@ describe('findEntrypoints', () => { const entrypoints = await findEntrypoints(); - expect(entrypoints).toEqual([]); + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'unlisted', + skipped: true, + }), + ]); }); }); describe('filterEntrypoints option', () => { - it('should control entrypoints accessible', async () => { + it('should override include/exclude of individual entrypoint options', async () => { globMock.mockResolvedValue([ 'options/index.html', 'popup/index.html', @@ -867,9 +917,25 @@ describe('findEntrypoints', () => { importEntrypointMock.mockResolvedValue({}); const entrypoints = await findEntrypoints(); - const names = entrypoints.map((item) => item.name); - expect(names).toHaveLength(2); - expect(names).toEqual(filterEntrypoints); + + expect(entrypoints).toEqual([ + expect.objectContaining({ + name: 'injected', + skipped: true, + }), + expect.objectContaining({ + name: 'options', + skipped: true, + }), + expect.objectContaining({ + name: 'popup', + skipped: false, + }), + expect.objectContaining({ + name: 'ui', + skipped: false, + }), + ]); }); }); }); diff --git a/packages/wxt/src/core/utils/building/__tests__/group-entrypoints.test.ts b/packages/wxt/src/core/utils/building/__tests__/group-entrypoints.test.ts index 413c4d9b..bb21a6fd 100644 --- a/packages/wxt/src/core/utils/building/__tests__/group-entrypoints.test.ts +++ b/packages/wxt/src/core/utils/building/__tests__/group-entrypoints.test.ts @@ -152,12 +152,16 @@ describe('groupEntrypoints', () => { options: { type: 'module', }, + skipped: false, + }); + const popup = fakePopupEntrypoint({ + skipped: false, }); - const popup = fakePopupEntrypoint(); const sandbox = fakeGenericEntrypoint({ inputPath: '/entrypoints/sandbox.html', name: 'sandbox', type: 'sandbox', + skipped: false, }); const actual = groupEntrypoints([background, popup, sandbox]); @@ -165,6 +169,22 @@ describe('groupEntrypoints', () => { expect(actual).toEqual([[background, popup], [sandbox]]); }); + it('should exclude skipped entrypoints from the groups to build', () => { + const background = fakeBackgroundEntrypoint({ + options: { + type: 'module', + }, + skipped: false, + }); + const popup = fakePopupEntrypoint({ + skipped: true, + }); + + const actual = groupEntrypoints([background, popup]); + + expect(actual).toEqual([[background]]); + }); + it.todo( 'should group ESM compatible sandbox scripts with sandbox pages', () => { diff --git a/packages/wxt/src/core/utils/building/find-entrypoints.ts b/packages/wxt/src/core/utils/building/find-entrypoints.ts index 72ac7436..0f71d07f 100644 --- a/packages/wxt/src/core/utils/building/find-entrypoints.ts +++ b/packages/wxt/src/core/utils/building/find-entrypoints.ts @@ -65,14 +65,7 @@ export async function findEntrypoints(): Promise { ); if (matchingGlob) { const type = PATH_GLOB_TO_TYPE_MAP[matchingGlob]; - results.push({ - name, - inputPath, - type, - skipped: - wxt.config.filterEntrypoints != null && - !wxt.config.filterEntrypoints.has(name), - }); + results.push({ name, inputPath, type }); } return results; }, []); @@ -84,7 +77,7 @@ export async function findEntrypoints(): Promise { // Import entrypoints to get their config let hasBackground = false; const env = createExtensionEnvironment(); - const entrypoints: Entrypoint[] = await env.run(() => + const entrypointsWithoutSkipped: Entrypoint[] = await env.run(() => Promise.all( entrypointInfos.map(async (info): Promise => { const { type } = info; @@ -130,61 +123,44 @@ export async function findEntrypoints(): Promise { ); if (wxt.config.command === 'serve' && !hasBackground) { - entrypoints.push( + entrypointsWithoutSkipped.push( await getBackgroundEntrypoint({ inputPath: VIRTUAL_NOOP_BACKGROUND_MODULE_ID, name: 'background', type: 'background', - skipped: false, }), ); } + // Mark entrypoints as skipped or not + const entrypoints = entrypointsWithoutSkipped.map((entry) => ({ + ...entry, + skipped: isEntrypointSkipped(entry), + })); + wxt.logger.debug('All entrypoints:', entrypoints); - const skippedEntrypointNames = entrypointInfos + const skippedEntrypointNames = entrypoints .filter((item) => item.skipped) .map((item) => item.name); if (skippedEntrypointNames.length) { wxt.logger.warn( - `Filter excluded the following entrypoints:\n${skippedEntrypointNames - .map((item) => `${pc.dim('-')} ${pc.cyan(item)}`) - .join('\n')}`, + [ + 'The following entrypoints have been skipped:', + ...skippedEntrypointNames.map( + (item) => `${pc.dim('-')} ${pc.cyan(item)}`, + ), + ].join('\n'), ); } - const targetEntrypoints = entrypoints.filter((entry) => { - const { include, exclude } = entry.options; - if (include?.length && exclude?.length) { - wxt.logger.warn( - `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint ignored.`, - ); - return false; - } - if (exclude?.length && !include?.length) { - return !exclude.includes(wxt.config.browser); - } - if (include?.length && !exclude?.length) { - return include.includes(wxt.config.browser); - } - if (skippedEntrypointNames.includes(entry.name)) { - return false; - } + await wxt.hooks.callHook('entrypoints:resolved', wxt, entrypoints); - return true; - }); - wxt.logger.debug(`${wxt.config.browser} entrypoints:`, targetEntrypoints); - await wxt.hooks.callHook('entrypoints:resolved', wxt, targetEntrypoints); - - return targetEntrypoints; + return entrypoints; } interface EntrypointInfo { name: string; inputPath: string; type: Entrypoint['type']; - /** - * @default false - */ - skipped: boolean; } function preventDuplicateEntrypointNames(files: EntrypointInfo[]) { @@ -252,7 +228,6 @@ async function getPopupEntrypoint( options: resolvePerBrowserOptions(options, wxt.config.browser), inputPath: info.inputPath, outputDir: wxt.config.outDir, - skipped: info.skipped, }; } @@ -275,7 +250,6 @@ async function getOptionsEntrypoint( options: resolvePerBrowserOptions(options, wxt.config.browser), inputPath: info.inputPath, outputDir: wxt.config.outDir, - skipped: info.skipped, }; } @@ -293,14 +267,12 @@ async function getUnlistedPageEntrypoint( inputPath: info.inputPath, outputDir: wxt.config.outDir, options, - skipped: info.skipped, }; } async function getUnlistedScriptEntrypoint({ inputPath, name, - skipped, }: EntrypointInfo): Promise { const defaultExport = await wxt.builder.importEntrypoint(inputPath); @@ -316,14 +288,12 @@ async function getUnlistedScriptEntrypoint({ inputPath, outputDir: wxt.config.outDir, options: resolvePerBrowserOptions(options, wxt.config.browser), - skipped, }; } async function getBackgroundEntrypoint({ inputPath, name, - skipped, }: EntrypointInfo): Promise { let options: Omit = {}; if (inputPath !== VIRTUAL_NOOP_BACKGROUND_MODULE_ID) { @@ -348,14 +318,12 @@ async function getBackgroundEntrypoint({ inputPath, outputDir: wxt.config.outDir, options: resolvePerBrowserOptions(options, wxt.config.browser), - skipped, }; } async function getContentScriptEntrypoint({ inputPath, name, - skipped, }: EntrypointInfo): Promise { const defaultExport = await wxt.builder.importEntrypoint(inputPath); @@ -377,7 +345,6 @@ async function getContentScriptEntrypoint({ inputPath, outputDir: resolve(wxt.config.outDir, CONTENT_SCRIPT_OUT_DIR), options: resolvePerBrowserOptions(options, wxt.config.browser), - skipped, }; } @@ -409,7 +376,6 @@ async function getSidepanelEntrypoint( options: resolvePerBrowserOptions(options, wxt.config.browser), inputPath: info.inputPath, outputDir: wxt.config.outDir, - skipped: info.skipped, }; } @@ -457,6 +423,29 @@ async function getHtmlEntrypointOptions( return options; } +function isEntrypointSkipped(entry: Omit): boolean { + if (wxt.config.filterEntrypoints != null) { + return !wxt.config.filterEntrypoints.has(entry.name); + } + + const { include, exclude } = entry.options; + if (include?.length && exclude?.length) { + wxt.logger.warn( + `The ${entry.name} entrypoint lists both include and exclude, but only one can be used per entrypoint. Entrypoint skipped.`, + ); + return true; + } + + if (exclude?.length && !include?.length) { + return exclude.includes(wxt.config.browser); + } + if (include?.length && !exclude?.length) { + return !include.includes(wxt.config.browser); + } + + return false; +} + const PATH_GLOB_TO_TYPE_MAP: Record = { 'sandbox.html': 'sandbox', 'sandbox/index.html': 'sandbox', diff --git a/packages/wxt/src/core/utils/building/group-entrypoints.ts b/packages/wxt/src/core/utils/building/group-entrypoints.ts index eb519fa9..6381bbd7 100644 --- a/packages/wxt/src/core/utils/building/group-entrypoints.ts +++ b/packages/wxt/src/core/utils/building/group-entrypoints.ts @@ -1,8 +1,8 @@ import { Entrypoint, EntrypointGroup } from '../../../types'; /** - * Entrypoints can be build in groups. HTML pages can all be built together in a single step, while - * content scripts must be build individually. + * Entrypoints are built in groups. HTML pages can all be built together in a single step, + * content scripts must be build individually, etc. * * This function returns the entrypoints put into these types of groups. */ @@ -11,6 +11,8 @@ export function groupEntrypoints(entrypoints: Entrypoint[]): EntrypointGroup[] { const groups: EntrypointGroup[] = []; for (const entry of entrypoints) { + if (entry.skipped) continue; + let group = ENTRY_TYPE_TO_GROUP_MAP[entry.type]; if (entry.type === 'background' && entry.options.type === 'module') { group = 'esm'; diff --git a/packages/wxt/src/types.ts b/packages/wxt/src/types.ts index 3205559c..80b428b0 100644 --- a/packages/wxt/src/types.ts +++ b/packages/wxt/src/types.ts @@ -45,6 +45,7 @@ export interface InlineConfig { * A list of entrypoint names (`"popup"`, `"options"`, etc.) to build. Will speed up the build if * your extension has lots of entrypoints, and you don't need to build all of them to develop a * feature. + * If specified, this completely overrides the `include`/`exclude` option provided per-entrypoint. */ filterEntrypoints?: string[]; /** @@ -728,7 +729,14 @@ export interface BaseEntrypoint { * subdirectory of it. */ outputDir: string; - skipped: boolean; + /** + * When true, the entrypoint will not be built by WXT. Normally this is set + * based on the `filterEntrypoints` config or the entrypoint's + * `include`/`exclude` options defined inside the file. + * + * See https://wxt.dev/guide/essentials/target-different-browsers.html#filtering-entrypoints + */ + skipped?: boolean; } export interface GenericEntrypoint extends BaseEntrypoint {