Skip to main content
Version: 0.4.1

Build System

TezosX Wallet is built with Vite 8 and the @crxjs/vite-plugin package, which handles Chrome extension specific concerns: content script injection, manifest transformation, and multiple entry points.

Entry points

The build produces four independent bundles:

EntryOutputLoaded by
src/ui/popup.tsxpopup.htmlExtension toolbar icon click
src/ui/approve.tsxapprove.htmlApprovalQueue.enqueue() popup window
src/content/bridge.tscontent script (ISOLATED)Chrome, injected on all pages
src/injected/provider.tscontent script (MAIN)Chrome, injected on all pages

Vite config highlights

File: packages/wallet/vite.config.ts

export default defineConfig({
base: '', // relative paths — required for Chrome extensions

define: {
global: 'globalThis', // Taquito / Beacon SDK expect Node's `global`
},

optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis', // also applied during dep pre-bundling
},
},
},

plugins: [
react(),
tailwindcss(),
crx({ manifest }), // CRXJS handles MV3 service worker + content scripts
],
});

Why global: 'globalThis' in two places

Vite's top-level define applies to source files. optimizeDeps.esbuildOptions.define applies to the esbuild pre-bundling step that processes node_modules. Both are needed because @taquito/local-forging contains an IIFE that checks typeof global !== "undefined" — without the second define, scope is undefined in the service worker context, crashing with TypeError: Cannot read properties of undefined (reading 'TextEncoder').

Commands

From the monorepo root:

# Development server (HMR for popup/approve, no HMR for content scripts)
pnpm wallet:dev

# Production build
pnpm wallet:build

# Type checking
pnpm wallet:typecheck

From inside packages/wallet/:

vite               # dev
vite build # production
tsc --noEmit # type check

Output structure

After pnpm wallet:build, packages/wallet/dist/ contains:

dist/
├── manifest.json # transformed by CRXJS
├── popup.html
├── approve.html
├── icons/
├── src/
│ ├── background/service-worker.js
│ ├── content/bridge.js
│ └── injected/provider.js
└── assets/
└── *.js / *.css # chunked popup + approve bundles

Load dist/ as an unpacked extension in chrome://extensions.

Content script worlds

The manifest declares two content script entries:

"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content/bridge.ts"],
"world": "ISOLATED",
"run_at": "document_start"
},
{
"matches": ["<all_urls>"],
"js": ["src/injected/provider.ts"],
"world": "MAIN",
"run_at": "document_start"
}
]

CRXJS compiles each with appropriate module settings. The MAIN world script uses a loader shim (provider.ts-loader.js) because Chrome cannot load ES modules directly in the MAIN world in dev mode.