Skip to content

Vite

Browsers run a service worker from a single script at a fixed address. Vite serves your modules one by one while you develop and gives built files hashed names, so it needs help with a worker. peer-cdn/vite gives it:

vite.config.ts
import { defineConfig } from 'vite';
import { peerCDN } from 'peer-cdn/vite';
export default defineConfig({
plugins: [peerCDN({ worker: 'src/sw.ts' })],
});
// in each page
await navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`);

With a relative base, such as ./, BASE_URL is ./ in a build, which a page reads from its own folder: a page in a folder below the base registers ../sw.js, one ../ for each folder. It needs Vite 8.

  • Bundles the worker from worker, relative to Vite’s root, into one classic script, which every browser can run as a service worker. Only what the worker imports goes in: PeerPlugin and peer-data stay in your pages.
  • Serves it at sw.js below your site’s base while vite runs, bundled afresh on each request, so a reload picks up your changes.
  • Writes it to sw.js at the top of the build when vite build runs, and vite preview serves it from there. A server build, as frameworks make for rendering pages, gets no worker.
  • Runs the signaling server of peer-cdn/server on Vite’s own server, in vite and vite preview. Pages reach it with io(), on the origin they came from. It needs socket.io installed: npm install -D socket.io. In middleware mode, where Vite has no server of its own, attach the signaling server to yours.

The worker is bundled with your config’s resolve.alias, define, mode and base, but without your config’s other plugins. For routes below your base, take the base from the worker’s own address, which is right whether the base is absolute or relative, as ./ is:

sw.ts
const base = new URL('./', location.href).pathname;
new PeerCDN().GET(`${base}assets`, ordered, cache.getMiddleware, peers.getMiddleware, network.getMiddleware).register();
Option What it is
worker The worker’s entry, relative to Vite’s root, such as src/sw.ts. Required.
signaling Run the signaling server on Vite’s dev and preview servers. true by default.

Turn signaling off when your pages signal some other way, such as through a server of your own; socket.io is then not needed. The signaling server answers every request below /socket.io/, so turn it off too when your dev server proxies a socket.io server of your own there. Other WebSockets on Vite’s server, such as its own and those server.proxy opens, are left alone.

vite build writes the worker with the rest of your site, so serve it as you serve the site. Vite’s servers only run while you develop, so run the signaling server on a Node.js server of your own, and give the pages its address:

new PeerPlugin({ signaling: new SocketChannel(io('https://signaling.example.com')) });

See Signaling for the server.

The example site runs on this config, with a second plugin that counts the pictures its server sends. It imports the plugin from the repository’s source, where your config imports it from peer-cdn/vite:

examples/vite.config.ts
import { fileURLToPath } from 'node:url';
import { defineConfig } from 'vite';
import { peerCDN } from '../src/vite.js';
// npm run examples: the example site on one dev server, with its service worker and signaling server.
export default defineConfig({
root: fileURLToPath(new URL('.', import.meta.url)),
resolve: { alias: { 'peer-cdn': fileURLToPath(new URL('../src/index.ts', import.meta.url)) } },
plugins: [
peerCDN({ worker: 'sw.ts' }),
{
// Counts the pictures the server sends, so the page can show that a second browser didn't need them.
name: 'served-counter',
configureServer(server) {
let served = 0;
server.middlewares.use('/assets/', (_request, _response, next) => {
served++;
next();
});
server.middlewares.use('/served', (_request, response) => {
response.setHeader('Content-Type', 'application/json');
response.setHeader('Cache-Control', 'no-store');
response.end(JSON.stringify({ served }));
});
},
},
],
});