- Rsbuild
- Vite
- Webpack
- Parcel
- Plain JavaScript
Rsbuild is a high-performance build tool based on Rspack. For Chrome extensions, you must disable code splitting to avoid Build the extension:
ChunkLoadError issues:Copy
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core';
export default defineConfig({
source: {
entry: {
background: './src/background/index.ts',
content: './src/content/index.ts',
},
},
output: {
target: 'web',
distPath: {
root: 'dist',
js: '',
},
filename: {
js: '[name].js',
},
copy: [
{ from: './manifest.json', to: './' },
],
charset: 'ascii',
},
html: {
inject: false,
},
tools: {
htmlPlugin: false,
rspack: {
output: {
chunkLoading: false,
asyncChunks: false,
},
optimization: {
splitChunks: false,
runtimeChunk: false,
},
},
},
performance: {
chunkSplit: { strategy: 'all-in-one' },
},
dev: {
hmr: false,
liveReload: false,
},
});
Copy
# Development build
npm run build
# Production build
NODE_ENV=production rsbuild build
The key configuration for Chrome extensions is disabling all code splitting (
asyncChunks: false, splitChunks: false, strategy: 'all-in-one'). This ensures all code is bundled into single files, avoiding dynamic import issues in content scripts.Chrome extensions require different build configurations for content scripts vs background/popup scripts:Build both configurations:
Copy
// vite.config.js
import { defineConfig } from 'vite';
import { resolve } from 'path';
// Content script needs IIFE format (no ES modules in content scripts)
const contentConfig = {
build: {
rollupOptions: {
input: {
content: resolve(__dirname, 'src/content.js')
},
output: {
format: 'iife',
entryFileNames: '[name].js',
inlineDynamicImports: true
}
},
outDir: 'dist',
emptyOutDir: false
}
};
// Background and popup can use ES modules
const mainConfig = {
build: {
rollupOptions: {
input: {
background: resolve(__dirname, 'src/background.js'),
popup: resolve(__dirname, 'src/popup.js')
},
output: {
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name].[ext]'
}
},
outDir: 'dist',
emptyOutDir: false
}
};
// Export based on mode: `vite build` vs `vite build --mode content`
export default defineConfig(({ mode }) => {
if (mode === 'content') {
return contentConfig;
}
return mainConfig;
});
Copy
# Build background and popup scripts
vite build
# Build content script separately
vite build --mode content
Content scripts run in an isolated context and cannot use ES modules. Use separate build configs with
format: 'iife' and inlineDynamicImports: true for content scripts.Copy
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
background: './src/background.js',
content: './src/content.js',
popup: './src/popup.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
composite: {
test: /[\\/]node_modules[\\/]@composite-inc\/composite-js/,
name: 'composite-vendor',
priority: 10
}
}
}
}
};
Parcel works with Chrome extensions using the Content Script - For analytics without session recording:Configuration Files:
@parcel/config-webextension plugin. However, Parcel’s web worker handling requires a special approach for the SDK.Background Script - Use the dedicated /background entry point which has no web worker dependencies:Copy
// src/background.ts
import { HttpTransport, createBackgroundHandler } from '@composite-inc/composite-js/background';
const transport = new HttpTransport({
apiKey: 'pk_your_api_key',
apiHost: 'https://api.composite.com',
});
createBackgroundHandler(transport);
Copy
// src/content-script.ts
import composite from '@composite-inc/composite-js/content-script';
await composite.init({
apiKey: 'pk_your_api_key',
transport: 'chrome-extension',
sessionRecording: false, // See note below
autocapture: true,
});
Copy
// .parcelrc
{
"extends": "@parcel/config-webextension"
}
Copy
// package.json
{
"scripts": {
"dev": "parcel watch manifest.json --host localhost",
"build": "parcel build manifest.json"
},
"dependencies": {
"@composite-inc/composite-js": "^0.0.12"
},
"devDependencies": {
"@parcel/config-webextension": "^2.12.0",
"parcel": "^2.12.0"
},
"alias": {
"@composite-inc/composite-js/background": "@composite-inc/composite-js/dist/background.mjs",
"@composite-inc/composite-js/content-script": "@composite-inc/composite-js/dist/content-script.mjs"
}
}
Session Recording Limitation: Session recording with Parcel has limited support due to web worker bundling differences. Set
sessionRecording: false for now, or use Webpack/Rsbuild for full session recording support.The
/background entry point is specifically designed for Parcel compatibility. It provides a minimal bundle with HttpTransport and createBackgroundHandler, avoiding web worker bundling issues.For extensions without a bundler, use the UMD build from CDN in your HTML files:
Copy
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@composite-inc/composite-js@latest/dist/composite.min.js"></script>
</head>
<body>
<script src="popup.js"></script>
</body>
</html>
Copy
// popup.js (no imports needed - composite is global)
document.addEventListener('DOMContentLoaded', async () => {
await window.composite.init({
apiKey: 'pk_your_api_key',
transport: 'chrome-extension'
});
// Identify user on login
document.getElementById('login-btn')?.addEventListener('click', async () => {
const user = await authenticateUser();
window.composite.identify(user.id, {
email: user.email,
plan: user.plan
});
});
});
Copy
// content.js (loaded via manifest, no imports)
(async () => {
await window.composite.init({
apiKey: 'pk_your_api_key',
transport: 'chrome-extension',
sessionRecording: true
});
})();
When using plain JavaScript, make sure to include the SDK script in your
manifest.json web_accessible_resources or load it in your HTML files.Dynamic Loading (Optional)
Load the SDK only when needed:Copy
// Lazy load for popup
document.addEventListener('DOMContentLoaded', async () => {
if (userHasOptedIn()) {
const { default: composite } = await import('@composite-inc/composite-js');
await composite.init({
apiKey: 'pk_your_api_key',
transport: 'chrome-extension'
});
}
});