Skip to content

Setup

This guide shows how to set up Epos with Vite, TypeScript, and Tailwind CSS.

You can skip this guide and just select the Vite + TypeScript + Tailwind CSS template when creating a new project in the app.epos.dev dashboard. That sets up all the necessary configuration. If you want to understand how it works or customize the setup, read on.

1. Create a Vite Project

First, scaffold a fresh project with Vite:

sh
npm create vite@latest
sh
pnpm create vite
sh
bun create vite
sh
yarn create vite

Follow the prompts to set up the project:

  1. Project name: Choose any name for your project.
  2. Framework: Select Vanilla. Do not select React here, because Epos already provides it.
  3. Variant: Choose your preference. This guide assumes you select TypeScript.

2. Install Epos

Install the epos package:

sh
npm install epos
sh
pnpm add epos
sh
bun add epos
sh
yarn add epos

This package provides TypeScript types for the Epos API and includes the Epos Vite plugin.

3. Install Tailwind CSS

Install Tailwind CSS and its Vite plugin:

sh
npm install -D tailwindcss @tailwindcss/vite
sh
pnpm add -D tailwindcss @tailwindcss/vite
sh
bun add -D tailwindcss @tailwindcss/vite
sh
yarn add -D tailwindcss @tailwindcss/vite

4. Create vite.config.ts

Start with this config:

ts
import tailwindcss from '@tailwindcss/vite'
import { epos } from 'epos/vite'
import { defineConfig } from 'vite'

export default defineConfig(({ mode }) => ({
  plugins: [epos(), tailwindcss()],
  build: {
    watch: mode === 'production' ? null : {},
    rolldownOptions: {
      input: {
        // Single entry point for now
        main: './src/main.tsx',
      },
      output: {
        // Avoid hashes in file names
        entryFileNames: '[name].js',
        assetFileNames: '[name].[ext]',
      },
    },
  },
}))

As you may know, Vite consists of two major parts:

  • A dev server that serves your application on localhost.
  • A build command that bundles your code into static output.

Epos works with actual built files, so we need to use the build command.

5. Update package.json

Update to use the build command:

json
{
  ...
  "scripts": {
    "dev": "vite --mode development", 
    "build": "tsc && vite build", 
    "preview": "vite preview"
    "dev": "vite build --mode development", 
    "build": "vite build --mode production", 
    "preview": "vite build --mode preview"
  },
  ...
}

This uses Vite as a bundler that writes files into dist.

6. Add jsx to tsconfig.json

Add the jsx option to tsconfig.json to enable JSX syntax:

json
{
  "compilerOptions": {
    "jsx": "react-jsx", 
    ...
  },
  "include": ["src"]
}

7. Create the Entry Files

Remove the files that Vite created for the starter app:

  • src/*
  • public/*
  • index.html

Create src/main.tsx and src/main.css:

tsx
import 'epos'
import './main.css'

const App = () => {
  return (
    <div className="p-4 font-sans">
      Epos + Vite
    </div>
  )
}

epos.render(<App />)
css
@import 'tailwindcss';

The import 'epos' line gives your editor types and autocomplete for the Epos API.

8. Create epos.json

Tell Epos to load the built files from dist.

json
{
  "$schema": "https://epos.dev/schema.json",
  "name": "My Extension",
  "targets": [
    {
      "matches": "<popup>",
      "load": ["dist/main.css", "dist/main.js"]
    }
  ]
}

Notice that epos.json points to bundled dist files, not src files.

9. Start the Build

Run the development build:

sh
npm run dev
sh
pnpm dev
sh
bun dev
sh
yarn dev

Vite rebuilds your project whenever you change source files, and Epos picks up the updated dist output.

10. Multiple Entry Points

If your project has more than one entry point, add them all to input:

ts
rolldownOptions: {
  input: {
    main: './src/main.tsx',
    background: './src/background.ts', 
  },
}

Then load the corresponding built files in epos.json:

json
"targets": [
  {
    "matches": "<popup>",
    "load": ["dist/main.css", "dist/main.js"]
  },
  {
    "matches": "<background>",
    "load": ["dist/background.js"]
  }
]

11. Shared Chunks and vite-plugin-rebundle

With multiple entry points, Vite may extract shared code into extra chunk files. That is normally ok, but Epos cannot load those chunks directly because they are imported dynamically.

To avoid that, you can use vite-plugin-rebundle, which keeps a single output file per entry point:

sh
npm install -D vite-plugin-rebundle
sh
pnpm add -D vite-plugin-rebundle
sh
bun add -D vite-plugin-rebundle
sh
yarn add -D vite-plugin-rebundle

Update vite.config.ts:

ts
import tailwindcss from '@tailwindcss/vite'
import { epos } from 'epos/vite'
import { defineConfig } from 'vite'
import { rebundle } from 'vite-plugin-rebundle'

export default defineConfig(({ mode }) => ({
  plugins: [
    epos(),
    tailwindcss(),
    rebundle({
      output: {
        minify: mode !== 'development', 
      }, 
    }), 
  ],
  build: {
    watch: mode === 'production' ? null : {},
    minify: false, 
    rolldownOptions: {
      input: {
        main: './src/main.tsx',
        background: './src/background.ts',
      },
      output: {
        entryFileNames: '[name].js',
        assetFileNames: '[name].[ext]',
      },
    },
  },
}))

Notice that minify is disabled for the normal Vite output and enabled for vite-plugin-rebundle. This way the files are minified only once, during rebundling.