fix(docker): fix TDZ crash, icon bundle bloat, rate-limit on static assets

- Fix "Cannot access 'a' before initialization" TDZ error after login
  caused by manualChunks splitting react-vendor + lucide icons into
  circular ES-module chunks. Removed manualChunks entirely.

- Replace `import * as icons from "lucide-react"` (pulls all ~1000 icons)
  with a targeted icon-map of ~50 icons actually used by tool definitions.
  Reduces shared icons chunk from 745KB to 62KB (132KB→16KB gzip).

- Exclude static files from @fastify/rate-limit via allowList so rapid
  page navigations don't 429 on JS/CSS chunk requests.

- Move Docker auth defaults (AUTH_ENABLED, DEFAULT_USERNAME,
  DEFAULT_PASSWORD) from Dockerfile ENV to entrypoint.sh runtime exports
  to avoid SecretsUsedInArgOrEnv warnings.

- Fix Docker CMD to use pnpm --filter for workspace-scoped tsx binary.

- Set COREPACK_HOME system-wide so non-root user can access pnpm cache.

- Lazy-load all pages in App.tsx and all controls in
  pipeline-step-settings.tsx to keep main bundle under 300KB.
This commit is contained in:
Siddharth Kumar Sah
2026-04-15 18:52:36 +08:00
parent 82073bba68
commit a4c63855d4
15 changed files with 295 additions and 133 deletions
+5 -8
View File
@@ -1,11 +1,11 @@
import type { CategoryInfo, Tool } from "@ashim/shared";
import { CATEGORIES, TOOLS } from "@ashim/shared";
import * as icons from "lucide-react";
import { Eye, EyeOff, FileImage, LayoutGrid, List, Search } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { GemLogo } from "@/components/common/gem-logo";
import { apiGet } from "@/lib/api";
import { ICON_MAP } from "@/lib/icon-map";
import { cn } from "@/lib/utils";
export function FullscreenGridPage() {
@@ -57,8 +57,6 @@ export function FullscreenGridPage() {
const activeCategories = CATEGORIES.filter((cat) => groupedTools.has(cat.id));
const iconsMap = icons as unknown as Record<string, React.ComponentType<{ className?: string }>>;
return (
<div className="min-h-screen bg-background text-foreground">
{/* Top bar */}
@@ -130,7 +128,6 @@ export function FullscreenGridPage() {
category={category}
tools={groupedTools.get(category.id) || []}
showDetails={showDetails}
iconsMap={iconsMap}
/>
))}
</div>
@@ -144,14 +141,13 @@ function CategoryCard({
category,
tools,
showDetails,
iconsMap,
}: {
category: CategoryInfo;
tools: Tool[];
showDetails: boolean;
iconsMap: Record<string, React.ComponentType<{ className?: string }>>;
}) {
const CategoryIcon = iconsMap[category.icon] || LayoutGrid;
const CategoryIcon =
(ICON_MAP[category.icon] as React.ComponentType<{ className?: string }>) ?? LayoutGrid;
return (
<div className="rounded-xl border border-border bg-card overflow-hidden shadow-sm hover:shadow-md transition-shadow">
@@ -183,7 +179,8 @@ function CategoryCard({
{/* Tool list */}
<div className="p-2">
{tools.map((tool) => {
const ToolIcon = iconsMap[tool.icon] || FileImage;
const ToolIcon =
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage;
return (
<Link
key={tool.id}
+6 -11
View File
@@ -1,11 +1,11 @@
import { CATEGORIES, TOOLS } from "@ashim/shared";
import * as icons from "lucide-react";
import { Loader2 } from "lucide-react";
import { useCallback, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { ImageViewer } from "@/components/common/image-viewer";
import { MultiImageViewer } from "@/components/common/multi-image-viewer";
import { AppLayout } from "@/components/layout/app-layout";
import { ICON_MAP } from "@/lib/icon-map";
import { useFileStore } from "@/stores/file-store";
import { useSettingsStore } from "@/stores/settings-store";
@@ -53,7 +53,7 @@ export function HomePage() {
{/* File info */}
<div className="p-4 border-b border-border">
<div className="flex items-center gap-2 text-sm">
<icons.CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
<ICON_MAP.CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
<span className="truncate font-medium text-foreground">
{selectedFileName ?? files[0].name}
</span>
@@ -81,9 +81,8 @@ export function HomePage() {
const tool = TOOLS.find((t) => t.id === id);
if (!tool) return null;
const Icon =
(icons as unknown as Record<string, React.ComponentType<{ className?: string }>>)[
tool.icon
] || icons.FileImage;
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ??
ICON_MAP.FileImage;
return (
<button
key={id}
@@ -120,12 +119,8 @@ export function HomePage() {
<div className="space-y-0.5">
{categoryTools.map((tool) => {
const Icon =
(
icons as unknown as Record<
string,
React.ComponentType<{ className?: string }>
>
)[tool.icon] || icons.FileImage;
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ??
ICON_MAP.FileImage;
return (
<button
key={tool.id}
+10 -4
View File
@@ -1,6 +1,12 @@
import { TOOLS } from "@ashim/shared";
import * as icons from "lucide-react";
import { CheckCircle2, ChevronLeft, ChevronRight, Download, Loader2 } from "lucide-react";
import {
CheckCircle2,
ChevronLeft,
ChevronRight,
Download,
FileImage,
Loader2,
} from "lucide-react";
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { Crop } from "react-image-crop";
import { useParams } from "react-router-dom";
@@ -17,6 +23,7 @@ import { EraserCanvas } from "@/components/tools/eraser-canvas";
import type { PreviewTransform } from "@/components/tools/rotate-settings";
import { useMobile } from "@/hooks/use-mobile";
import { formatFileSize } from "@/lib/download";
import { ICON_MAP } from "@/lib/icon-map";
import { getToolRegistryEntry } from "@/lib/tool-registry";
import { useFileStore } from "@/stores/file-store";
@@ -222,8 +229,7 @@ export function ToolPage() {
}
const IconComponent =
(icons as unknown as Record<string, React.ComponentType<{ className?: string }>>)[tool.icon] ||
icons.FileImage;
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage;
const hasFile = files.length > 0;
const hasProcessed = !!processedUrl;