// Copyright 2026 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // @ts-check import { rmSync } from 'node:fs'; import { join, relative } from 'node:path'; import { defineConfig } from 'rolldown'; import type { RolldownOptions } from 'rolldown'; import { transform } from 'oxc-transform'; const external = [ // Native libraries '@signalapp/libsignal-client', /^@signalapp\/libsignal-client\/.*/, '@signalapp/ringrtc', '@signalapp/sqlcipher', '@signalapp/mute-state-change', '@signalapp/windows-ucv', '@indutny/simple-windows-notifications', '@indutny/mac-screen-share', '@napi-rs/canvas', 'electron', 'fs-xattr', 'fsevents', 'mac-screen-capture-permissions', 'sass', // Large libraries (3.7mb total) 'google-libphonenumber', // Imported, but not used in production builds 'mocha', // Electron's internal module 'original-fs', // Uses fast-glob and dynamic requires './preload_test.preload.ts', ]; const isProd = process.argv.some(arg => arg === '--minify'); const sandboxPreload = { // Preload 'preload/about': 'ts/windows/about/preload.preload.ts', 'preload/calldiagnostic': 'ts/windows/calldiagnostic/preload.preload.ts', 'preload/debuglog': 'ts/windows/debuglog/preload.preload.ts', 'preload/permissions': 'ts/windows/permissions/preload.preload.ts', 'preload/screenShare': 'ts/windows/screenShare/preload.preload.ts', 'preload/sticker-creator': 'ts/windows/sticker-creator/preload.preload.ts', }; const sandboxDOM = { 'dom/about': 'ts/windows/about/app.dom.tsx', 'dom/calldiagnostic': 'ts/windows/calldiagnostic/app.dom.tsx', 'dom/debuglog': 'ts/windows/debuglog/app.dom.tsx', 'dom/loading': 'ts/windows/loading/start.dom.ts', 'dom/permissions': 'ts/windows/permissions/app.dom.tsx', 'dom/screenShare': 'ts/windows/screenShare/app.dom.tsx', }; const defaults = { transform: { jsx: 'react-jsx', define: { 'process.env.IS_BUNDLED': 'true', ...(isProd ? { __REACT_DEVTOOLS_GLOBAL_HOOK__: 'undefined', } : {}), }, }, plugins: [ { name: 'NODE_ENV', transform(code, id) { if (id.endsWith('.json')) { return; } const path = relative(__dirname, id); if (path.startsWith('app')) { return; } return transform(id, code, { define: { 'process.env.NODE_ENV': isProd ? '"production"' : '"development"', }, sourcemap: !isProd, }); }, }, ], external, output: { format: 'cjs', dir: 'bundles', exports: 'named', chunkFileNames: 'chunks/[name]-[hash].js', generatedCode: { symbols: false, }, sourcemap: !isProd, sourcemapBaseUrl: 'bundles:///', postBanner: ({ fileName }) => { // See preload.wrapper.ts if (fileName === 'preload/main.js') { return '(function(require, __dirname, exports){'; } return ''; }, postFooter: ({ fileName }) => { const lines = new Array(); // See preload.wrapper.ts if (fileName === 'preload/main.js') { lines.push('})'); } lines.push(`//# sourceURL=bundles:///${fileName}`); return lines.join('\n'); }, }, watch: { clearScreen: false, }, } satisfies RolldownOptions; if (isProd) { try { rmSync(join(__dirname, 'bundles'), { recursive: true }); } catch (error) { if ( typeof error === 'object' && error != null && 'code' in error && error.code !== 'ENOENT' ) { throw error; } } } export default defineConfig([ // Each sandboxed bundle has to be separate from the rest since // they cannot use `require()` ...Object.entries(sandboxPreload).map(([key, value]): RolldownOptions => { return { ...defaults, external: ['electron'], platform: 'browser', input: { [key]: value }, output: { ...defaults.output, codeSplitting: false, }, }; }), { ...defaults, external: ['electron'], platform: 'browser', input: sandboxDOM, output: { ...defaults.output, format: 'es', }, }, { ...defaults, input: { // Main main: 'app/main.main.ts', config: 'app/config.main.js', // Preloads 'preload/wrapper': 'preload.wrapper.ts', 'preload/main': 'ts/windows/main/preload.preload.ts', // Workers 'workers/sql': 'ts/sql/mainWorker.node.ts', 'workers/heic': 'ts/workers/heicConverterWorker.node.ts', }, }, // Voice Note Worker { input: 'ts/workers/mp3Encoder.std.ts', transform: { define: { process: 'undefined', require: 'undefined', eval: 'undefined', }, }, output: { file: 'bundles/workers/mp3Encoder.js', exports: 'named', generatedCode: { symbols: false, }, codeSplitting: false, }, watch: { clearScreen: false, }, }, ]);