Skip to content

feat: add IIFE support for main-world content scripts#1102

Open
Toumash wants to merge 6 commits intocrxjs:mainfrom
Toumash:iife-content-scripts
Open

feat: add IIFE support for main-world content scripts#1102
Toumash wants to merge 6 commits intocrxjs:mainfrom
Toumash:iife-content-scripts

Conversation

@Toumash
Copy link
Copy Markdown
Member

@Toumash Toumash commented Jan 16, 2026

Summary

Adds support for IIFE content scripts via the ?iife import query, enabling main-world script injection using chrome.scripting.registerContentScripts with world: 'MAIN'.

Features

  • IIFE bundling: Scripts imported with ?iife query are bundled as IIFE (Immediately Invoked Function Expression) for injection into the page's main world
  • Dev mode support: IIFE scripts rebuild on file change and trigger extension reload so Chrome picks up the updated content
  • Build support: IIFE scripts are properly bundled during production builds
  • TypeScript support: Added module declaration for *?iife imports in client.d.ts
  • Vite compatibility: Works with Vite 3, 5, 6, and 7

Usage

// background.ts
import mainWorld from './main-world?iife'

chrome.scripting.registerContentScripts([{
  id: 'main-world',
  js: [mainWorld],
  matches: ['<all_urls>'],
  world: 'MAIN',
  runAt: 'document_start',
}])

Technical Details

  • IIFE scripts are bundled separately using Vite's build API, not part of Vite's module graph
  • Path format: No leading / for registerContentScripts (e.g., main-world.js)
  • Dev mode: File changes trigger IIFE rebuild + crx:runtime-reload (Chrome caches registered content scripts)
  • IIFE scripts don't support HMR - extension reload is required for changes to take effect

Files Changed

  • packages/vite-plugin/src/node/fileWriter-rxjs.ts - IIFE bundling for dev mode
  • packages/vite-plugin/src/node/plugin-contentScripts.ts - IIFE bundling for build
  • packages/vite-plugin/src/node/plugin-contentScripts_dynamic.ts - ?iife query handling
  • packages/vite-plugin/src/node/plugin-hmr.ts - IIFE rebuild detection and runtime reload
  • packages/vite-plugin/client.d.ts - TypeScript module declaration

Testing

# Output tests
pnpm vitest --run --mode out tests/out/dynamic-script-iife

# E2E tests (includes rebuild-on-change test)
pnpm vitest --run --mode e2e tests/e2e/mv3-dynamic-script-iife

Closes #1101

demo:

crxjs_demo_1101.mp4

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Jan 16, 2026

🦋 Changeset detected

Latest commit: deea812

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@crxjs/vite-plugin Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Toumash Toumash changed the title feat: add iife support for main-world content scripts draft: feat: add iife support for main-world content scripts Jan 16, 2026
@salmin89
Copy link
Copy Markdown
Contributor

this is not quite what I'm looking for.

?iife imports code, but registerContentScript expects a string (file path).

My usage is like this:

import filePath from './content/iife?iife'

const script: chrome.scripting.RegisteredContentScript = {
  id: 'abc',
  matches: ['<all_urls>'],
  js: [filePath],
  runAt: 'document_start',
  world: 'MAIN',
}

;(async () => {
  const existing = await chrome.scripting.getRegisteredContentScripts({
    ids: [script.id],
  })

  if (existing.length) {
    await chrome.scripting.updateContentScripts([script])
  } else {
    await chrome.scripting.registerContentScripts([script])
  }
})()

You might still keep this PR, for iife in background scripts. And I'll use my work-arounds for now.

@Toumash
Copy link
Copy Markdown
Member Author

Toumash commented Jan 17, 2026

Thanks for the clarification! I’ve updated the branch so ?iife behaves as an alias of ?script&iife and resolves to a file path string for registerContentScripts. Tests/fixtures were updated accordingly, im working on the rebuild not being picked up right now and the test for it

@Toumash Toumash changed the title draft: feat: add iife support for main-world content scripts feat: add IIFE support for main-world content scripts Jan 17, 2026
Enable IIFE bundling for main-world content scripts and add output/e2e coverage for dynamic script injection.
Allow ?iife imports to resolve to content script file names for registerContentScripts use, and align fixtures with the new alias.
- Simplify test fixture to demonstrate the primary use case for IIFE imports
- Update background.ts to use chrome.scripting.registerContentScripts
- Fix IIFE path format (no leading slash) for registerContentScripts compatibility
- Remove unused files (options.ts, content1.ts, content2.ts, redirect.html)
- Handle document_start timing in main-world.ts (wait for body)
- Detect IIFE script changes in handleHotUpdate using raw file path
  since IIFE scripts are not in Vite's module graph
- Trigger runtime reload after IIFE rebuild completes so Chrome picks
  up the updated file (Chrome caches content scripts registered via
  registerContentScripts)
- Update E2E test to verify IIFE rebuild on file change:
  - Use src1/src2 fixture pattern for testing file updates
  - Verify that page reload after file change shows updated content
@Toumash Toumash force-pushed the iife-content-scripts branch from 7575600 to d1ec973 Compare January 17, 2026 13:59
@Toumash
Copy link
Copy Markdown
Member Author

Toumash commented Jan 17, 2026

Ok, now the main world script should rebuild just fine

@Toumash Toumash changed the title feat: add IIFE support for main-world content scripts draft: feat: add IIFE support for main-world content scripts Jan 17, 2026
@Toumash Toumash force-pushed the iife-content-scripts branch from d5e1c9a to 6a391c1 Compare January 17, 2026 15:29
@Toumash Toumash changed the title draft: feat: add IIFE support for main-world content scripts feat: add IIFE support for main-world content scripts Jan 17, 2026
Vite 6+ tracks plugins internally using WeakMaps, causing errors when
reusing plugins in a separate Rollup build. This rewrites bundleIife()
to use viteBuild() with direct rollupOptions instead of raw Rollup.

Key changes:
- Use configFile: false to avoid loading user's config with incompatible settings
- Use direct rollupOptions with format: 'iife' instead of lib mode
- Copy resolve settings (alias, extensions, conditions) from dev server
- Filter out manifest.json and .vite/ assets from output

Works with Vite 3, 5, 6, and 7.
@Toumash Toumash force-pushed the iife-content-scripts branch from 6a391c1 to deea812 Compare January 17, 2026 15:30
@Toumash
Copy link
Copy Markdown
Member Author

Toumash commented Jan 17, 2026

hmm i only wanted to have full tests for everything between vite versions

@Toumash Toumash force-pushed the iife-content-scripts branch from 71a1bee to deea812 Compare January 17, 2026 17:22
@Toumash
Copy link
Copy Markdown
Member Author

Toumash commented Jan 18, 2026

@salmin89 see the demo in the description. Unfortunately tests on windows fail probably because of the same reasons as other e2e were failing before but i dont remember correctly

@Toumash Toumash requested a review from FliPPeDround January 25, 2026 13:15
@Toumash
Copy link
Copy Markdown
Member Author

Toumash commented Apr 14, 2026

@copilot resolve the merge conflicts in this pull request

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.

Support IIFE bundles for main world content scripts

3 participants