// SPDX-License-Identifier: AGPL-3.0-or-later import {existsSync, mkdirSync, readdirSync, writeFileSync} from 'node:fs'; import path, {dirname} from 'node:path'; import {fileURLToPath} from 'node:url'; import { Compilation, CopyRspackPlugin, DefinePlugin, HtmlRspackPlugin, LightningCssMinimizerRspackPlugin, SwcJsMinimizerRspackPlugin, sources, } from '@rspack/core'; import {getLinguiSwcPluginConfig} from './scripts/build/rspack/lingui.mjs'; import {staticFilesPlugin} from './scripts/build/rspack/static-files.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const ROOT_DIR = path.resolve(__dirname, '.'); const MONOREPO_ROOT = path.resolve(__dirname, '..'); const SRC_DIR = path.join(ROOT_DIR, 'src'); const DIST_DIR = path.join(ROOT_DIR, 'dist'); const PKGS_DIR = path.join(ROOT_DIR, 'pkgs'); const PUBLIC_DIR = path.join(ROOT_DIR, 'assets'); const BROWSER_ASSERT_STRICT_MODULE = path.join(SRC_DIR, 'features', 'platform', 'utils', 'BrowserAssertStrict.ts'); const DEFAULT_DEV_SERVER_PORT = 3000; const BOOT_PRELOAD_CHUNK_GROUPS = ['app-bootstrap', 'boot-app']; const BOOT_PRELOAD_FONT_FILES = [ 'FluxerSans/FluxerSans-Regular.woff2', 'FluxerSans/FluxerSans-SemiBold.woff2', 'FluxerSans/FluxerSans-Bold.woff2', ]; const BOOT_LOW_PRIORITY_FONT_FILES = ['FluxerSans/FluxerSans-Medium.woff2']; const AUTH_ENTRY_DOCUMENT = 'auth-index.html'; const ENTRY_STYLESHEET_LINK_PATTERN = //gu; const CSS_URL_PATTERN = /url\(\s*['"]?([^'")]+)/gu; const DESKTOP_MODULE_ASSET_QUERY = /[?&]m=([a-z][a-z0-9_]{0,63})(?:&|$)/u; const RESERVED_DESKTOP_MODULE_NAMES = new Set(['assets', 'fluxer_renderer']); const DESKTOP_MODULE_ASSET_SOURCES = [ { test: /[\\/]fonts[\\/]files[\\/]FluxerSans(JP|KR|SC|TC)[\\/][^\\/]+\.woff2$/u, moduleName: (match) => `fluxer_fonts_${match[1].toLowerCase()}`, }, { test: /[\\/]@arborium[\\/]([a-z0-9-]+)[\\/]grammar_bg\.wasm$/u, moduleName: (match) => `fluxer_grammar_${match[1].replace(/-/gu, '_')}`, }, { test: /[\\/]deepfilternet3[\\/](df_bg\.wasm|[^\\/]+\.tar\.gz)$/u, moduleName: () => 'fluxer_deepfilter', }, { test: /[\\/]onnxruntime-web[\\/]dist[\\/][^\\/]+\.wasm$|[\\/]camera-effects[\\/]models[\\/][^\\/]+\.onnx$/u, moduleName: () => 'fluxer_camera_effects', }, ]; class UnusableDesktopModuleQueryError extends Error { constructor(resource) { super( `${resource} carries an ?m= desktop module query that is not a usable module name. It must match [a-z][a-z0-9_]{0,63} and cannot be assets or fluxer_renderer.`, ); this.name = 'UnusableDesktopModuleQueryError'; } } function desktopModuleAssetName(resource) { const match = DESKTOP_MODULE_ASSET_QUERY.exec(resource); if (match == null || RESERVED_DESKTOP_MODULE_NAMES.has(match[1])) { throw new UnusableDesktopModuleQueryError(resource); } return match[1]; } function desktopModuleAssetRules(isProduction) { return DESKTOP_MODULE_ASSET_SOURCES.map((source) => ({ test: source.test, type: 'asset/resource', generator: { filename: (pathData) => { const match = source.test.exec(pathData.filename ?? ''); if (match == null) { throw new UnusableDesktopModuleQueryError(pathData.filename ?? ''); } const moduleName = source.moduleName(match); const extension = pathData.filename.endsWith('.tar.gz') ? '.tar.gz' : '[ext]'; return isProduction ? `assets/${moduleName}/[contenthash:16]${extension}` : `assets/${moduleName}/[name].[hash]${extension}`; }, }, })); } function resolveMode() { const modeIndex = process.argv.indexOf('--mode'); if (modeIndex >= 0) { const modeValue = process.argv[modeIndex + 1]; if (modeValue) { return modeValue; } } return 'production'; } const ACCEPTED_CSS_ORDER_CONFLICTS = [ ['app/components/dialogs/ConfirmModal', 'ui/button/Button'], ['app/components/dialogs/Modal', 'ui/button/Button'], ['app/components/dialogs/components/SettingsModalHeader', 'ui/button/Button'], ['app/components/dialogs/shared/SettingsHeadingLinkButton', 'ui/button/Button'], ['ui/tooltip/Tooltip', 'ui/button/Button'], ]; function isAcceptedCssOrderConflict(warning) { const message = warning.message ?? ''; if (!message.includes('Conflicting order between')) { return false; } return ACCEPTED_CSS_ORDER_CONFLICTS.some(([first, second]) => { const firstPath = `./src/features/${first}.module.css`; const secondPath = `./src/features/${second}.module.css`; return ( message.includes(`between ${firstPath} and ${secondPath}`) || message.includes(`between ${secondPath} and ${firstPath}`) ); }); } function isMainRuntimeChunk(chunk) { const runtime = chunk.runtime; if (runtime == null) { return chunk.name === 'main'; } if (typeof runtime === 'string') { return runtime === 'main'; } if (typeof runtime[Symbol.iterator] === 'function') { for (const name of runtime) { if (name !== 'main') return false; } return true; } return false; } function mirrorCacheGroups(groups) { const mirrored = {}; for (const [key, group] of Object.entries(groups)) { if (group.chunks != null) { mirrored[key] = group; continue; } mirrored[key] = {...group, chunks: (chunk) => isMainRuntimeChunk(chunk) && chunk.canBeInitial()}; mirrored[`${key}Async`] = { ...group, name: `${group.name}-async`, chunks: (chunk) => isMainRuntimeChunk(chunk) && !chunk.canBeInitial(), }; } return mirrored; } function bootPreloadPlugin() { return { apply(compiler) { compiler.hooks.thisCompilation.tap('BootPreloadPlugin', (compilation) => { compilation.hooks.processAssets.tap( {name: 'BootPreloadPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_REPORT}, () => { const asset = compilation.getAsset('index.html'); if (asset == null) { return; } const html = String(asset.source.source()); if (!html.includes('')) { return; } const configuredPublicPath = compilation.outputOptions.publicPath; const base = typeof configuredPublicPath === 'string' && configuredPublicPath !== 'auto' ? configuredPublicPath : '/'; const files = new Set(); for (const name of BOOT_PRELOAD_CHUNK_GROUPS) { for (const group of compilation.chunkGroups) { if (group.name !== name) { continue; } for (const file of group.getFiles()) { if (!html.includes(file)) { files.add(file); } } } } const ordered = [...files]; const links = [ ...ordered .filter((file) => file.endsWith('.css')) .map((file) => ``), ...ordered .filter((file) => file.endsWith('.js')) .map((file) => ``), ].join(''); if (links === '') { return; } compilation.updateAsset('index.html', new sources.RawSource(html.replace('', `${links}`))); }, ); }); }, }; } class UninlinableEntryStylesheetError extends Error { constructor(reason) { super(`Cannot inline the entry stylesheet into ${AUTH_ENTRY_DOCUMENT}: ${reason}`); this.name = 'UninlinableEntryStylesheetError'; } } function inlineEntryStylesheet(css, href) { const content = css.replace(/\/\*# sourceMappingURL=[^*]*\*\/\s*$/u, ''); if (content.includes('${content}`; } function authEntryDocumentPlugin() { return { apply(compiler) { compiler.hooks.thisCompilation.tap('AuthEntryDocumentPlugin', (compilation) => { compilation.hooks.processAssets.tap( {name: 'AuthEntryDocumentPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_REPORT}, () => { const asset = compilation.getAsset('index.html'); if (asset == null) { return; } const html = String(asset.source.source()); const links = [...html.matchAll(ENTRY_STYLESHEET_LINK_PATTERN)]; if (links.length === 0) { throw new UninlinableEntryStylesheetError('index.html links no stylesheet'); } let document = html; for (const [link, href] of links) { const name = compilation .getAssets() .find((candidate) => href.endsWith(`/${candidate.name}`) || href === candidate.name)?.name; if (name == null) { throw new UninlinableEntryStylesheetError(`${href} is not an emitted asset`); } const css = String(compilation.getAsset(name).source.source()); document = document.replace(link, () => inlineEntryStylesheet(css, href)); } compilation.emitAsset(AUTH_ENTRY_DOCUMENT, new sources.RawSource(document)); }, ); }); }, }; } function fontPreloadPlugin() { return { apply(compiler) { compiler.hooks.thisCompilation.tap('FontPreloadPlugin', (compilation) => { HtmlRspackPlugin.getCompilationHooks(compilation).alterAssetTagGroups.tap('FontPreloadPlugin', (data) => { const fonts = []; const lowPriority = new Set(BOOT_LOW_PRIORITY_FONT_FILES); for (const sourceName of [...BOOT_PRELOAD_FONT_FILES, ...BOOT_LOW_PRIORITY_FONT_FILES]) { const asset = compilation .getAssets() .find((candidate) => String(candidate.info.sourceFilename ?? '').endsWith(sourceName)); if (asset == null) { throw new Error(`Cannot preload ${sourceName}: no emitted asset came from it`); } fonts.push({ tagName: 'link', voidTag: true, attributes: { rel: 'preload', as: 'font', type: 'font/woff2', crossorigin: '', href: `${data.publicPath}${asset.name}`, ...(lowPriority.has(sourceName) ? {fetchpriority: 'low'} : {}), }, }); } data.headTags.unshift(...fonts); return data; }); }); }, }; } function nodeAssertStrictSchemePlugin() { return { apply(compiler) { compiler.hooks.normalModuleFactory.tap('NodeAssertStrictSchemePlugin', (factory) => { factory.hooks.beforeResolve.tap('NodeAssertStrictSchemePlugin', (resolveData) => { if (resolveData.request === 'node:assert/strict') { resolveData.request = BROWSER_ASSERT_STRICT_MODULE; } }); }); }, }; } const mode = resolveMode(); const isProduction = mode === 'production'; const isDevelopment = !isProduction; const isDesktopRenderer = envString('FLUXER_DESKTOP_RENDERER') === 'true'; const devJsName = 'assets/[name].js'; const devCssName = 'assets/[name].css'; const productionJsName = 'assets/[contenthash:16].js'; const productionWorkerJsName = 'assets/[contenthash:16].worker.js'; const devCorsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization', }; const devNoStoreHeaders = { 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0', Pragma: 'no-cache', Expires: '0', 'CDN-Cache-Control': 'no-store', 'Cloudflare-CDN-Cache-Control': 'no-store', }; function envString(name, fallback = undefined) { const value = process.env[name]; if (value === undefined || value === null || value === '') { return fallback; } return value; } function withTrailingSlash(value) { return value.endsWith('/') ? value : `${value}/`; } function devServerHeaders() { return {...devCorsHeaders, ...devNoStoreHeaders}; } function resolveReleaseChannel() { const value = envString('PUBLIC_RELEASE_CHANNEL', envString('RELEASE_CHANNEL', 'canary')).trim().toLowerCase(); if (value === 'stable' || value === 'canary' || value === 'development') { return value; } throw new Error(`PUBLIC_RELEASE_CHANNEL must be stable, canary, or development, received ${JSON.stringify(value)}`); } function resolvePublicValues() { return { PUBLIC_BUILD_VERSION: envString('PUBLIC_BUILD_VERSION', envString('BUILD_VERSION', 'dev')), PUBLIC_RELEASE_CHANNEL: resolveReleaseChannel(), }; } function getPublicEnvVar(values, name) { const value = values[name]; return value === undefined ? 'undefined' : JSON.stringify(value); } function getChunkRuntimeNames(runtime) { if (runtime == null) { return []; } if (typeof runtime === 'string') { return [runtime]; } if (typeof runtime[Symbol.iterator] === 'function') { return [...runtime].filter((name) => typeof name === 'string'); } return []; } function getChunkPathNames(pathData) { const names = []; const chunkName = pathData.chunk?.name; if (typeof chunkName === 'string') { names.push(chunkName); } names.push(...getChunkRuntimeNames(pathData.runtime)); names.push(...getChunkRuntimeNames(pathData.chunk?.runtime)); return names; } function isWorkerPath(pathData) { return getChunkPathNames(pathData).some((name) => name.endsWith('.worker')); } function jsFilename(pathData) { if (pathData.chunk?.name === 'sw') { return 'sw.js'; } if (!isProduction) { return devJsName; } return isWorkerPath(pathData) ? productionWorkerJsName : productionJsName; } export default () => { const linguiSwcPlugin = getLinguiSwcPluginConfig(); const publicValues = resolvePublicValues(); const assetBaseUrl = isDesktopRenderer ? undefined : envString('PUBLIC_ASSET_BASE_URL'); const staticCdnEndpoint = isDesktopRenderer ? '' : envString('PUBLIC_STATIC_CDN_ENDPOINT', envString('FLUXER_STATIC_CDN_ENDPOINT', isProduction ? '' : '')); function resolveArboriumWasmAliases() { const arbDir = path.join(ROOT_DIR, 'node_modules', '@arborium'); const aliases = {}; try { for (const pkg of readdirSync(arbDir)) { const grammarWasm = path.join(arbDir, pkg, 'grammar_bg.wasm'); if (!existsSync(grammarWasm)) continue; const internalWasm = `arborium_${pkg.replace(/-/g, '_')}_plugin_bg.wasm`; aliases[internalWasm] = grammarWasm; aliases[`@arborium/${pkg}/${internalWasm}`] = grammarWasm; } } catch {} return aliases; } const normalizedStaticCdnEndpoint = staticCdnEndpoint?.replace(/\/+$/, '') ?? ''; const workerWasmPublicPath = isProduction && normalizedStaticCdnEndpoint ? `${normalizedStaticCdnEndpoint}/` : undefined; const productionPublicPath = assetBaseUrl !== undefined ? withTrailingSlash(assetBaseUrl) : '/'; const developmentPublicPath = normalizedStaticCdnEndpoint ? `${normalizedStaticCdnEndpoint}/` : '/'; const publicPath = isProduction ? productionPublicPath : developmentPublicPath; return { mode, entry: isDesktopRenderer ? {main: path.join(SRC_DIR, 'index.tsx')} : { main: path.join(SRC_DIR, 'index.tsx'), sw: path.join(SRC_DIR, 'features', 'platform', 'service_worker', 'Worker.ts'), }, output: { path: DIST_DIR, publicPath, workerPublicPath: '/', workerChunkLoading: false, filename: jsFilename, chunkFilename: jsFilename, cssFilename: isProduction ? 'assets/[contenthash:16].css' : devCssName, cssChunkFilename: isProduction ? 'assets/[contenthash:16].css' : devCssName, assetModuleFilename: isProduction ? 'assets/[contenthash:16][ext]' : 'assets/[name].[hash][ext]', webAssemblyModuleFilename: isProduction ? 'assets/[contenthash:16].wasm' : 'assets/[name].[hash].wasm', clean: true, }, devtool: 'source-map', target: ['web', 'browserslist'], lazyCompilation: false, performance: false, ignoreWarnings: [isAcceptedCssOrderConflict], resolve: { alias: { ...resolveArboriumWasmAliases(), '@arborium/arborium/arborium_host_bg.wasm': path.resolve( ROOT_DIR, 'node_modules/@arborium/arborium/dist/arborium_host_bg.wasm', ), '@app': SRC_DIR, '@fluxer/voice_engine_v2/bridge': path.join( MONOREPO_ROOT, 'packages', 'voice_engine_v2', 'src', 'bridge', 'index.ts', ), '@fluxer/voice_engine_v2/runtime': path.join( MONOREPO_ROOT, 'packages', 'voice_engine_v2', 'src', 'runtime', 'index.ts', ), '@fluxer/voice_engine_v2/testing': path.join( MONOREPO_ROOT, 'packages', 'voice_engine_v2', 'src', 'testing', 'index.ts', ), '@fluxer': path.join(MONOREPO_ROOT, 'packages'), '@pkgs': PKGS_DIR, 'assert/strict': BROWSER_ASSERT_STRICT_MODULE, 'livekit-client$': path.join(PKGS_DIR, 'livekit-client/src/index.ts'), 'livekit-client/e2ee-worker': path.join(PKGS_DIR, 'livekit-client/src/e2ee/worker/e2ee.worker.ts'), 'node:assert/strict': BROWSER_ASSERT_STRICT_MODULE, }, extensions: ['.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.tsx', '.ts', '.jsx', '.js', '.json', '.mjs', '.cjs'], extensionAlias: { '.js': ['.js', '.tsx', '.ts'], '.mjs': ['.mjs', '.mts'], '.cjs': ['.cjs', '.cts'], }, conditionNames: ['import', 'module', 'webpack', 'browser', 'default'], mainFields: ['browser', 'module', 'main'], }, module: { rules: [ { test: /[\\/]@arborium[\\/]arborium[\\/]dist[\\/]arborium\.js$/, use: [{loader: path.join(ROOT_DIR, 'scripts/build/rspack/local-arborium-loader.cjs')}], parser: { wrappedContextRegExp: /^\b\B$/u, exprContextCritical: false, wrappedContextCritical: false, }, }, { test: /[\\/]@sapphi-red[\\/]web-noise-suppressor[\\/]dist[\\/][^\\/]+[\\/]workletProcessor\.js$/, type: 'asset/resource', use: [{loader: path.join(ROOT_DIR, 'scripts/build/rspack/noise-suppressor-worklet-loader.cjs')}], generator: { filename: isProduction ? 'assets/[contenthash:16].worklet.js' : 'assets/[name].[hash].worklet.js', }, }, { test: /[\\/]src[\\/].+\.worklet\.js$/, type: 'asset/resource', generator: { filename: isProduction ? 'assets/[contenthash:16].worklet.js' : 'assets/[name].[hash].worklet.js', }, }, { test: /\.(tsx|ts|jsx|js)$/, exclude: [/node_modules/, /\.worklet\.js$/], type: 'javascript/auto', parser: { dynamicImport: true, }, use: { loader: 'builtin:swc-loader', options: { jsc: { parser: { syntax: 'typescript', tsx: true, }, transform: { react: { runtime: 'automatic', development: isDevelopment, refresh: false, }, }, experimental: { plugins: [linguiSwcPlugin], }, target: 'es2015', }, }, }, }, { test: /\.module\.css$/, use: [{loader: 'postcss-loader'}], type: 'css/module', parser: {namedExports: false, dashedIdents: false, grid: false, container: false}, }, { test: /[\\/]@phosphor-icons[\\/]react[\\/]dist[\\/]defs[\\/][^\\/]+\.es\.js$/, use: [{loader: path.join(ROOT_DIR, 'scripts/build/rspack/phosphor-unused-weights-loader.cjs')}], }, { test: /[\\/]node_modules[\\/]katex[\\/]dist[\\/]katex(\.min)?\.css$/, use: [{loader: path.join(ROOT_DIR, 'scripts/build/rspack/katex-legacy-fonts-loader.cjs')}], type: 'css', }, { test: /\.css$/, exclude: /\.module\.css$/, use: [{loader: 'postcss-loader'}], type: 'css', }, { test: /\.svg$/, issuer: /\.[jt]sx?$/, resourceQuery: /react/, type: 'javascript/auto', use: [ { loader: 'builtin:swc-loader', options: { jsc: { parser: {syntax: 'typescript', tsx: true}, transform: {react: {runtime: 'automatic', development: isDevelopment}}, target: 'es2015', }, }, }, { loader: '@svgr/webpack', options: { babel: false, typescript: true, jsxRuntime: 'automatic', svgoConfig: { plugins: [ { name: 'preset-default', params: {overrides: {removeViewBox: false}}, }, ], }, }, }, ], }, { test: /\.svg$/, resourceQuery: {not: [/react/]}, type: 'asset/resource', }, { test: /\.wasm$/, type: 'asset/resource', generator: { filename: isProduction ? 'assets/[contenthash:16][ext]' : 'assets/[name].[hash][ext]', ...(workerWasmPublicPath ? {publicPath: workerWasmPublicPath} : {}), }, }, { test: /[\\/]deepfilternet3[\\/][^\\/]+\.tar\.gz$/, type: 'asset/resource', generator: { filename: isProduction ? 'assets/[contenthash:16].tar.gz' : 'assets/[name].[hash].tar.gz', }, }, { test: /\.onnx$/, type: 'asset/resource', generator: { filename: isProduction ? 'assets/[contenthash:16][ext]' : 'assets/[name].[hash][ext]', }, }, { test: /\.(png|jpg|jpeg|gif|webp|avif|ico|woff|woff2|ttf|eot|mp3|wav|ogg|mp4|webm)$/, type: 'asset/resource', generator: { filename: isProduction ? 'assets/[contenthash:16][ext]' : 'assets/[name].[hash][ext]', }, }, ...(isDesktopRenderer ? desktopModuleAssetRules(isProduction) : []), { resourceQuery: DESKTOP_MODULE_ASSET_QUERY, type: 'asset/resource', generator: { filename: (pathData) => { const moduleName = desktopModuleAssetName(pathData.filename ?? ''); return isProduction ? `assets/${moduleName}/[contenthash:16][ext]` : `assets/${moduleName}/[name].[hash][ext]`; }, }, }, ], generator: { 'css/module': { localIdentName: '[name]__[local]___[hash:base64:6]', exportsConvention: 'camel-case-only', exportsOnly: false, }, 'css/auto': { localIdentName: '[name]__[local]___[hash:base64:6]', exportsConvention: 'camel-case-only', exportsOnly: false, }, }, }, plugins: [ nodeAssertStrictSchemePlugin(), fontPreloadPlugin(), ...(isProduction ? [bootPreloadPlugin()] : []), ...(isProduction && !isDesktopRenderer ? [authEntryDocumentPlugin()] : []), new HtmlRspackPlugin({ template: path.join(ROOT_DIR, 'index.html'), filename: 'index.html', hash: isDevelopment, inject: 'body', scriptLoading: 'module', excludeChunks: ['sw'], ...(isDesktopRenderer ? {publicPath: '/'} : {}), }), new CopyRspackPlugin({ patterns: [ { from: PUBLIC_DIR, to: DIST_DIR, noErrorOnMissing: true, }, ...(isDesktopRenderer ? [ { from: path.join(MONOREPO_ROOT, 'fluxer_static', 'web'), to: path.join(DIST_DIR, 'web'), }, ] : []), ], }), staticFilesPlugin({ staticCdnEndpoint: normalizedStaticCdnEndpoint, fontsDir: path.join(MONOREPO_ROOT, 'packages', 'fonts'), wasmCratesDir: path.join(ROOT_DIR, 'rust'), }), new DefinePlugin({ define: 'undefined', __FLUXER_PRECACHE_MANIFEST__: JSON.stringify([]), __FLUXER_SW_VERSION__: JSON.stringify(publicValues.PUBLIC_BUILD_VERSION || 'dev'), 'process.env.NODE_ENV': JSON.stringify(mode), 'import.meta.env.DEV': JSON.stringify(isDevelopment), 'import.meta.env.PROD': JSON.stringify(isProduction), 'import.meta.env.MODE': JSON.stringify(mode), 'import.meta.env.PUBLIC_BUILD_VERSION': getPublicEnvVar(publicValues, 'PUBLIC_BUILD_VERSION'), 'import.meta.env.PUBLIC_RELEASE_CHANNEL': getPublicEnvVar(publicValues, 'PUBLIC_RELEASE_CHANNEL'), }), { apply(compiler) { compiler.hooks.afterEmit.tap('VersionJsonPlugin', () => { const versionData = { version: publicValues.PUBLIC_BUILD_VERSION, }; mkdirSync(DIST_DIR, {recursive: true}); writeFileSync(path.join(DIST_DIR, 'version.json'), JSON.stringify(versionData)); }); }, }, ], optimization: { splitChunks: isProduction ? { chunks: (chunk) => isMainRuntimeChunk(chunk), maxInitialRequests: 15, maxAsyncSize: 2_000_000, cacheGroups: mirrorCacheGroups({ icons: { test: /[\\/]node_modules[\\/]@phosphor-icons[\\/]/, name: 'icons', priority: 60, reuseExistingChunk: true, }, highlight: { test: /[\\/]node_modules[\\/](@arborium[\\/]arborium[\\/]|\.pnpm[\\/]@arborium\+arborium@)/, name: 'highlight', priority: 55, reuseExistingChunk: true, enforce: true, chunks: (chunk) => !chunk.canBeInitial() && !isWorkerPath({chunk}), }, tts: { test: /[\\/]node_modules[\\/]mespeak[\\/]/, name: 'tts', priority: 54, reuseExistingChunk: true, enforce: true, chunks: (chunk) => !chunk.canBeInitial() && !isWorkerPath({chunk}), }, editor: { test: /[\\/]node_modules[\\/](@codemirror|@lezer|codemirror|style-mod|w3c-keyname|crelt|@marijn)[\\/]/, name: 'editor', priority: 53, reuseExistingChunk: true, enforce: true, chunks: (chunk) => !chunk.canBeInitial() && !isWorkerPath({chunk}), }, livekit: { test: /[\\/](node_modules|pkgs)[\\/](livekit-client|@livekit)[\\/]/, name: 'livekit', priority: 50, reuseExistingChunk: true, }, katex: { test: /[\\/]node_modules[\\/]katex[\\/]/, name: 'katex', priority: 48, reuseExistingChunk: true, maxSize: 100_000, }, animation: { test: /[\\/]node_modules[\\/](framer-motion|motion|motion-dom|motion-utils)[\\/]/, name: 'animation', priority: 45, reuseExistingChunk: true, }, mobx: { test: /[\\/]node_modules[\\/](mobx|mobx-react-lite|mobx-persist-store)[\\/]/, name: 'mobx', priority: 43, reuseExistingChunk: true, }, i18n: { test: /[\\/]node_modules[\\/]@lingui[\\/]/, name: 'i18n', priority: 42, reuseExistingChunk: true, enforce: true, }, colorPicker: { test: /[\\/]node_modules[\\/](@react-aria[\\/]color|@react-stately[\\/]color)[\\/]/, name: 'color-picker', priority: 42, reuseExistingChunk: true, enforce: true, }, reactAria: { test: /[\\/]node_modules[\\/](react-aria-components|@react-aria|@react-stately|@internationalized)[\\/]/, name: 'react-aria', priority: 41, reuseExistingChunk: true, }, validation: { test: /[\\/]node_modules[\\/](valibot)[\\/]/, name: 'validation', priority: 39, reuseExistingChunk: true, }, datetime: { test: /[\\/]node_modules[\\/]luxon[\\/]/, name: 'datetime', priority: 38, reuseExistingChunk: true, }, observable: { test: /[\\/]node_modules[\\/]rxjs[\\/]/, name: 'observable', priority: 37, reuseExistingChunk: true, }, unicode: { test: /[\\/]node_modules[\\/](idna-uts46-hx|emoji-regex)[\\/]/, name: 'unicode', priority: 36, reuseExistingChunk: true, }, dnd: { test: /[\\/]node_modules[\\/](@dnd-kit|react-dnd)[\\/]/, name: 'dnd', priority: 33, reuseExistingChunk: true, }, radix: { test: /[\\/]node_modules[\\/]@radix-ui[\\/]/, name: 'radix', priority: 31, reuseExistingChunk: true, }, ui: { test: /[\\/]node_modules[\\/](react-select|react-hook-form|@floating-ui)[\\/]/, name: 'ui', priority: 30, reuseExistingChunk: true, }, utils: { test: /[\\/]node_modules[\\/](lodash|clsx|thumbhash|match-sorter)[\\/]/, name: 'utils', priority: 28, reuseExistingChunk: true, }, networking: { test: /[\\/]node_modules[\\/](ws)[\\/]/, name: 'networking', priority: 26, reuseExistingChunk: true, }, react: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'react', priority: 25, reuseExistingChunk: true, }, platform: { test: /[\\/]node_modules[\\/]bowser[\\/]/, name: 'platform', priority: 27, reuseExistingChunk: true, enforce: true, }, phone: { test: /[\\/]node_modules[\\/]libphonenumber-js[\\/]/, name: 'phone', priority: 26, reuseExistingChunk: true, enforce: true, }, qrcode: { test: /[\\/]node_modules[\\/](qrcode|dijkstrajs)[\\/]/, name: 'qrcode', priority: 24, reuseExistingChunk: true, enforce: true, }, lexical: { test: /[\\/]node_modules[\\/](lexical|@lexical)[\\/]/, name: 'lexical', priority: 23, reuseExistingChunk: true, enforce: true, }, calendar: { test: /[\\/]node_modules[\\/](react-day-picker|date-fns|@date-fns)[\\/]/, name: 'calendar', priority: 22, reuseExistingChunk: true, enforce: true, }, webrtc: { test: /[\\/]node_modules[\\/](webrtc-adapter|sdp|sdp-transform)[\\/]/, name: 'webrtc', priority: 21, reuseExistingChunk: true, enforce: true, }, noiseFilter: { test: /[\\/]node_modules[\\/]deepfilternet3-noise-filter[\\/]/, name: 'noise-filter', priority: 20, reuseExistingChunk: true, enforce: true, }, virtualizer: { test: /[\\/]node_modules[\\/]@tanstack[\\/]/, name: 'virtualizer', priority: 19, reuseExistingChunk: true, enforce: true, }, vendor: { test: (module) => { if (!module.resource) return false; if (!/[\\/]node_modules[\\/]/.test(module.resource)) return false; if (/[\\/](@arborium|@phosphor-icons|katex)[\\/]/.test(module.resource)) return false; if (/[\\/]\.pnpm[\\/]@arborium\+/.test(module.resource)) return false; return true; }, name: 'vendor', priority: 10, reuseExistingChunk: true, }, }), } : false, runtimeChunk: false, moduleIds: isProduction ? 'deterministic' : 'named', chunkIds: isProduction ? 'deterministic' : 'named', minimize: isProduction, minimizer: [ new SwcJsMinimizerRspackPlugin({ compress: true, mangle: true, format: {comments: false}, exclude: /\.worklet\.js$/, }), new LightningCssMinimizerRspackPlugin(), ], }, devServer: { port: Number(process.env.FLUXER_APP_DEV_PORT) || DEFAULT_DEV_SERVER_PORT, hot: false, liveReload: false, client: false, webSocketServer: false, historyApiFallback: true, allowedHosts: 'all', headers: isDevelopment ? devServerHeaders : devCorsHeaders, static: { directory: DIST_DIR, watch: false, }, }, }; };