Skip to main content
CRXJS works seamlessly with Vite’s configuration system. This guide covers recommended Vite settings and CRXJS-specific considerations.

Basic Configuration

Create or update your vite.config.ts file:
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [
    crx({ manifest })
  ],
  build: {
    // Recommended: keep exports in content scripts for dynamic imports
    rollupOptions: {
      preserveEntrySignatures: 'exports-only'
    }
  }
})

Essential Settings

Entry Points

You typically don’t need to manually configure build.rollupOptions.input with CRXJS. The plugin automatically extracts entry points from your manifest.
CRXJS automatically configures entry points based on your manifest:
  • background.service_worker or background.scripts
  • content_scripts[].js
  • action.default_popup
  • options_page or options_ui.page
  • devtools_page
  • side_panel.default_path

Preserve Entry Signatures

CRXJS automatically sets preserveEntrySignatures: 'exports-only' to support the Content Script Module API. This allows content scripts to export an onExecute function.
export default defineConfig({
  build: {
    rollupOptions: {
      // CRXJS sets this automatically
      preserveEntrySignatures: 'exports-only'
    }
  }
})

Output Directory

build.outDir
string
Directory where the built extension is output.Default: 'dist'
export default defineConfig({
  build: {
    outDir: 'dist/chrome-extension'
  }
})

Development Server

Port Configuration

server.port
number
Development server port. During development, your extension loads resources from http://localhost:[port].Default: 5173
export default defineConfig({
  server: {
    port: 3000,
    strictPort: true, // Fail if port is already in use
  }
})
Ensure your development port is consistent. Changing the port requires reloading the extension in Chrome.

HTTPS Configuration

For testing features that require HTTPS (like camera/microphone access):
export default defineConfig({
  server: {
    https: true,
    port: 5173
  }
})

HMR Configuration

server.hmr
boolean | object
Hot Module Replacement settings. CRXJS provides custom HMR for content scripts.
export default defineConfig({
  server: {
    hmr: {
      protocol: 'ws',
      host: 'localhost',
      port: 5173
    }
  }
})

Build Options

Source Maps

build.sourcemap
boolean | 'inline' | 'hidden'
Generate source maps for debugging. CRXJS automatically includes source maps in web_accessible_resources.Default: false
export default defineConfig({
  build: {
    sourcemap: true // or 'inline' or 'hidden'
  }
})
Enable source maps in development for easier debugging. They’re automatically added to web_accessible_resources so Chrome DevTools can load them.

Minification

export default defineConfig({
  build: {
    minify: 'terser', // or 'esbuild'
    terserOptions: {
      compress: {
        drop_console: true, // Remove console.log in production
      }
    }
  }
})

Target Browsers

build.target
string | string[]
JavaScript compatibility target. Chrome extensions run in Chrome, so you can use modern JavaScript.Default: 'modules'
export default defineConfig({
  build: {
    target: 'esnext' // Use latest JavaScript features
  }
})

Multi-Browser Configuration

To build for both Chrome and Firefox:
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig(({ mode }) => {
  const browser = mode === 'firefox' ? 'firefox' : 'chrome'
  
  return {
    plugins: [
      crx({ 
        manifest,
        browser 
      })
    ],
    build: {
      outDir: `dist/${browser}`
    }
  }
})
Build commands:
# Build for Chrome
vite build

# Build for Firefox
vite build --mode firefox

Framework Integration

React

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [
    react(),
    crx({ manifest })
  ]
})
CRXJS automatically configures React Fast Refresh for content scripts when @vitejs/plugin-react is detected.

Vue

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [
    vue(),
    crx({ manifest })
  ]
})

Svelte

import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'

export default defineConfig({
  plugins: [
    svelte(),
    crx({ manifest })
  ]
})

Path Aliases

Configure path aliases for cleaner imports:
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils')
    }
  }
})
Usage:
import Button from '@components/Button'
import { formatDate } from '@utils/date'

Environment Variables

CRXJS supports Vite’s environment variable system:
// vite.config.ts
export default defineConfig({
  define: {
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version)
  }
})
Create .env files:
# .env.development
VITE_API_URL=http://localhost:3000

# .env.production
VITE_API_URL=https://api.example.com
Access in your code:
const apiUrl = import.meta.env.VITE_API_URL
Only variables prefixed with VITE_ are exposed to your extension code. Never commit sensitive credentials to .env files.

Complete Example

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
import path from 'path'

export default defineConfig(({ mode }) => {
  const isDev = mode === 'development'
  const browser = process.env.TARGET_BROWSER || 'chrome'
  
  return {
    plugins: [
      react(),
      crx({ 
        manifest,
        browser: browser as 'chrome' | 'firefox',
        contentScripts: {
          hmrTimeout: isDev ? 10000 : 5000
        }
      })
    ],
    resolve: {
      alias: {
        '@': path.resolve(__dirname, './src')
      }
    },
    server: {
      port: 5173,
      strictPort: true
    },
    build: {
      outDir: `dist/${browser}`,
      sourcemap: isDev,
      minify: !isDev,
      rollupOptions: {
        output: {
          manualChunks: {
            // Split vendor code for better caching
            vendor: ['react', 'react-dom']
          }
        }
      }
    }
  }
})

See Also

Build docs developers (and LLMs) love