feat: Add globalName entrypoint option. (#2017)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { TestProject } from '../utils';
|
||||
import type { GenericEntrypoint, InlineConfig } from '../../src';
|
||||
import type { InlineConfig, UnlistedScriptEntrypoint } from '../../src';
|
||||
import { readFile } from 'fs-extra';
|
||||
import { normalizePath } from '../../src';
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('Module Helpers', () => {
|
||||
'export default defineBackground(() => {})',
|
||||
);
|
||||
|
||||
const entrypoint: GenericEntrypoint = {
|
||||
const entrypoint: UnlistedScriptEntrypoint = {
|
||||
type: 'unlisted-script',
|
||||
inputPath: project.resolvePath('modules/test/injected.ts'),
|
||||
name: 'injected',
|
||||
|
||||
@@ -459,4 +459,104 @@ describe('Output Directory Structure', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
describe('globalName option', () => {
|
||||
it('generates an IIFE with a default name', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/content.js',
|
||||
`export default defineContentScript({
|
||||
matches: ["*://*/*"],
|
||||
main() {},
|
||||
})`,
|
||||
);
|
||||
|
||||
await project.build({ vite: () => ({ build: { minify: false } }) });
|
||||
|
||||
const output = await project.serializeFile(
|
||||
'.output/chrome-mv3/content-scripts/content.js',
|
||||
);
|
||||
expect(output).toMatch(/^var content\s?=[\s\S]*^content;$/gm);
|
||||
});
|
||||
|
||||
it('generates an IIFE with a specific name', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/content.js',
|
||||
`export default defineContentScript({
|
||||
globalName: "MyContentScript",
|
||||
matches: ["*://*/*"],
|
||||
main() {},
|
||||
})`,
|
||||
);
|
||||
|
||||
await project.build({ vite: () => ({ build: { minify: false } }) });
|
||||
|
||||
const output = await project.serializeFile(
|
||||
'.output/chrome-mv3/content-scripts/content.js',
|
||||
);
|
||||
expect(output).toMatch(
|
||||
/^var MyContentScript =[\s\S]*^MyContentScript;$/gm,
|
||||
);
|
||||
});
|
||||
|
||||
it('generates an IIFE with a specific name provided by a function', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/content.js',
|
||||
`export default defineContentScript({
|
||||
globalName: () => "MyContentScript",
|
||||
matches: ["*://*/*"],
|
||||
main() {},
|
||||
})`,
|
||||
);
|
||||
|
||||
await project.build({ vite: () => ({ build: { minify: false } }) });
|
||||
|
||||
const output = await project.serializeFile(
|
||||
'.output/chrome-mv3/content-scripts/content.js',
|
||||
);
|
||||
expect(output).toMatch(
|
||||
/^var MyContentScript =[\s\S]*^MyContentScript;$/gm,
|
||||
);
|
||||
});
|
||||
|
||||
it('generates an anonymous IIFE when not minified', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/content.js',
|
||||
`export default defineContentScript({
|
||||
globalName: false,
|
||||
matches: ["*://*/*"],
|
||||
main() {},
|
||||
})`,
|
||||
);
|
||||
|
||||
await project.build({ vite: () => ({ build: { minify: false } }) });
|
||||
|
||||
const output = await project.serializeFile(
|
||||
'.output/chrome-mv3/content-scripts/content.js',
|
||||
);
|
||||
expect(output).toMatch(/^\(function\(\) {[\s\S]*^}\)\(\);$/gm);
|
||||
});
|
||||
|
||||
it('generates an anonymous IIFE when minified', async () => {
|
||||
const project = new TestProject();
|
||||
project.addFile(
|
||||
'entrypoints/content.js',
|
||||
`export default defineContentScript({
|
||||
globalName: false,
|
||||
matches: ["*://*/*"],
|
||||
main() {},
|
||||
})`,
|
||||
);
|
||||
|
||||
await project.build({ vite: () => ({ build: { minify: true } }) });
|
||||
|
||||
const output = await project.serializeFile(
|
||||
'.output/chrome-mv3/content-scripts/content.js',
|
||||
);
|
||||
expect(output).toMatch(/^\(function\(\){[\s\S]*}\)\(\);$/gm);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ export async function createViteBuilder(
|
||||
const plugins: NonNullable<vite.UserConfig['plugins']> = [
|
||||
wxtPlugins.entrypointGroupGlobals(entrypoint),
|
||||
];
|
||||
const iifeReturnValueName = safeVarName(entrypoint.name);
|
||||
let iifeReturnValueName = safeVarName(entrypoint.name);
|
||||
|
||||
if (
|
||||
entrypoint.type === 'content-script-style' ||
|
||||
@@ -122,7 +122,17 @@ export async function createViteBuilder(
|
||||
entrypoint.type === 'content-script' ||
|
||||
entrypoint.type === 'unlisted-script'
|
||||
) {
|
||||
plugins.push(wxtPlugins.iifeFooter(iifeReturnValueName));
|
||||
if (typeof entrypoint.options.globalName === 'string') {
|
||||
iifeReturnValueName = entrypoint.options.globalName;
|
||||
} else if (typeof entrypoint.options.globalName === 'function') {
|
||||
iifeReturnValueName = entrypoint.options.globalName(entrypoint);
|
||||
}
|
||||
|
||||
if (entrypoint.options.globalName === false) {
|
||||
plugins.push(wxtPlugins.iifeAnonymous(iifeReturnValueName));
|
||||
} else {
|
||||
plugins.push(wxtPlugins.iifeFooter(iifeReturnValueName));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
export function iifeAnonymous(iifeReturnValueName: string): Plugin {
|
||||
return {
|
||||
name: 'wxt:iife-anonymous',
|
||||
generateBundle(_, bundle) {
|
||||
for (const chunk of Object.values(bundle)) {
|
||||
if (chunk.type === 'chunk' && chunk.isEntry) {
|
||||
const namedIIFEPrefix = new RegExp(
|
||||
`^var ${iifeReturnValueName}\\s*=\\s*(\\(function)`,
|
||||
);
|
||||
chunk.code = chunk.code.replace(namedIIFEPrefix, '$1');
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -14,3 +14,4 @@ export * from './removeEntrypointMainFunction';
|
||||
export * from './wxtPluginLoader';
|
||||
export * from './resolveAppConfig';
|
||||
export * from './iifeFooter';
|
||||
export * from './iifeAnonymous';
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SidepanelEntrypoint,
|
||||
MainWorldContentScriptEntrypointOptions,
|
||||
IsolatedWorldContentScriptEntrypointOptions,
|
||||
UnlistedScriptEntrypoint,
|
||||
} from '../../../types';
|
||||
import fs from 'fs-extra';
|
||||
import { minimatch } from 'minimatch';
|
||||
@@ -319,7 +320,7 @@ async function getUnlistedPageEntrypoint(
|
||||
async function getUnlistedScriptEntrypoint(
|
||||
{ inputPath, name }: EntrypointInfo,
|
||||
options: Record<string, any>,
|
||||
): Promise<GenericEntrypoint> {
|
||||
): Promise<UnlistedScriptEntrypoint> {
|
||||
return {
|
||||
type: 'unlisted-script',
|
||||
name,
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Wxt,
|
||||
SidepanelEntrypoint,
|
||||
BaseEntrypoint,
|
||||
UnlistedScriptEntrypoint,
|
||||
} from '../../../types';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { vi } from 'vitest';
|
||||
@@ -99,7 +100,7 @@ export const fakeBackgroundEntrypoint = fakeObjectCreator<BackgroundEntrypoint>(
|
||||
);
|
||||
|
||||
export const fakeUnlistedScriptEntrypoint =
|
||||
fakeObjectCreator<GenericEntrypoint>(() => ({
|
||||
fakeObjectCreator<UnlistedScriptEntrypoint>(() => ({
|
||||
type: 'unlisted-script',
|
||||
inputPath: fakeFile('src'),
|
||||
name: faker.string.alpha(),
|
||||
@@ -186,7 +187,6 @@ export const fakeGenericEntrypoint = fakeObjectCreator<GenericEntrypoint>(
|
||||
'newtab',
|
||||
'devtools',
|
||||
'unlisted-page',
|
||||
'unlisted-script',
|
||||
]),
|
||||
inputPath: fakeFile('src'),
|
||||
name: faker.string.alpha(),
|
||||
|
||||
@@ -566,7 +566,28 @@ export interface BackgroundEntrypointOptions extends BaseEntrypointOptions {
|
||||
type?: PerBrowserOption<'module'>;
|
||||
}
|
||||
|
||||
export interface BaseContentScriptEntrypointOptions extends BaseEntrypointOptions {
|
||||
export interface BaseScriptEntrypointOptions extends BaseEntrypointOptions {
|
||||
/**
|
||||
* The variable name for the IIFE in the output bundle.
|
||||
*
|
||||
* This option is relevant for scripts inserted into the page context where the default IIFE
|
||||
* variable name may conflict with an existing variable on the target page. This applies to content
|
||||
* scripts with world=MAIN, and others, such as unlisted scripts, that could be dynamically injected
|
||||
* into the page with a <script> tag.
|
||||
*
|
||||
* Available options:
|
||||
* - `true`: automatically generate a name for the IIFE based on the entrypoint name
|
||||
* - `false`: Output the IIFE without a variable name, making it anonymous. This is the safest option
|
||||
* to avoid conflicts with existing variables on the page. This will become the default in a future version of WXT.
|
||||
* - `string`: Use the provided string as the global variable name.
|
||||
* - `function`: A function that receives the entrypoint and returns a string to use as the variable name.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
globalName?: string | boolean | ((entrypoint: Entrypoint) => string);
|
||||
}
|
||||
|
||||
export interface BaseContentScriptEntrypointOptions extends BaseScriptEntrypointOptions {
|
||||
matches?: PerBrowserOption<NonNullable<ManifestContentScript['matches']>>;
|
||||
/**
|
||||
* See https://developer.chrome.com/docs/extensions/mv3/content_scripts/
|
||||
@@ -749,12 +770,16 @@ export interface GenericEntrypoint extends BaseEntrypoint {
|
||||
| 'newtab'
|
||||
| 'devtools'
|
||||
| 'unlisted-page'
|
||||
| 'unlisted-script'
|
||||
| 'unlisted-style'
|
||||
| 'content-script-style';
|
||||
options: ResolvedPerBrowserOptions<BaseEntrypointOptions>;
|
||||
}
|
||||
|
||||
export interface UnlistedScriptEntrypoint extends BaseEntrypoint {
|
||||
type: 'unlisted-script';
|
||||
options: ResolvedPerBrowserOptions<BaseScriptEntrypointOptions>;
|
||||
}
|
||||
|
||||
export interface BackgroundEntrypoint extends BaseEntrypoint {
|
||||
type: 'background';
|
||||
options: ResolvedPerBrowserOptions<BackgroundEntrypointOptions>;
|
||||
@@ -786,6 +811,7 @@ export interface SidepanelEntrypoint extends BaseEntrypoint {
|
||||
export type Entrypoint =
|
||||
| GenericEntrypoint
|
||||
| BackgroundEntrypoint
|
||||
| UnlistedScriptEntrypoint
|
||||
| ContentScriptEntrypoint
|
||||
| PopupEntrypoint
|
||||
| OptionsEntrypoint
|
||||
@@ -835,7 +861,7 @@ export interface BackgroundDefinition extends BackgroundEntrypointOptions {
|
||||
main(): void;
|
||||
}
|
||||
|
||||
export interface UnlistedScriptDefinition extends BaseEntrypointOptions {
|
||||
export interface UnlistedScriptDefinition extends BaseScriptEntrypointOptions {
|
||||
/**
|
||||
* Main function executed when the unlisted script is ran.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user