docs: Update ESBuild migration plan documentation
Updated ESBUILD_MIGRATION_PLAN.md to reflect: - Phase 1 completion status - New Alpine.js conversion work - Remaining templates status (docs.templ excluded per user request)
This commit is contained in:
+367
-36
@@ -366,12 +366,17 @@ import { querySelector, querySelectorAll, getElementById, createElement, showEle
|
||||
// Systematic replacement of all 54 instances
|
||||
```
|
||||
|
||||
### 1.4: Restructure Function Wrapping (Option A)
|
||||
### 1.4: Restructure Function Wrapping
|
||||
|
||||
**web/src/themeDropdown.ts** - Eliminate function wrapping
|
||||
This section eliminates the anti-pattern of wrapping functions from other files. Currently, `themeDropdown.ts` overrides `header.ts` functions by reading from window and reassigning them. We'll convert this to proper ES module composition.
|
||||
|
||||
**Current (WRONG)**:
|
||||
---
|
||||
|
||||
#### 1.4.1: web/src/themeDropdown.ts - Complete Rewrite
|
||||
|
||||
**Current Problem (Lines 1-32):**
|
||||
```typescript
|
||||
// Anti-pattern: Reading and reassigning window globals
|
||||
const originalToggleThemeDropdown = (window as any).toggleThemeDropdown;
|
||||
(window as any).toggleThemeDropdown = () => {
|
||||
originalToggleThemeDropdown();
|
||||
@@ -380,64 +385,390 @@ const originalToggleThemeDropdown = (window as any).toggleThemeDropdown;
|
||||
};
|
||||
```
|
||||
|
||||
**New Approach**:
|
||||
```typescript
|
||||
import { toggleThemeDropdown as originalToggle } from "./header";
|
||||
import { updateWoodPanelingIndicators } from "./woodPaneling";
|
||||
**Step-by-Step Conversion:**
|
||||
|
||||
export function initializeThemeDropdown() {
|
||||
// Call original function
|
||||
originalToggle();
|
||||
// Then update indicators
|
||||
**1. Add ES Module Imports (at top of file, after line 1):**
|
||||
```typescript
|
||||
import { toggleThemeDropdown } from "./header";
|
||||
import { updateWoodPanelingIndicators } from "./woodPaneling";
|
||||
```
|
||||
|
||||
**2. Remove window read/override (delete lines 1-8):**
|
||||
```typescript
|
||||
// DELETE THESE LINES:
|
||||
// const originalToggleThemeDropdown = (window as any).toggleThemeDropdown;
|
||||
// (window as any).toggleThemeDropdown = () => {
|
||||
// originalToggleThemeDropdown();
|
||||
// updateThemeIndicators();
|
||||
// (window as any).updateWoodPanelingIndicators?.();
|
||||
// };
|
||||
```
|
||||
|
||||
**3. Create new composed function (replace deleted code with):**
|
||||
```typescript
|
||||
// Compose the theme dropdown behavior
|
||||
export function initializeThemeDropdown(): void {
|
||||
// Call original function from header.ts
|
||||
toggleThemeDropdown();
|
||||
// Then update theme indicators
|
||||
updateThemeIndicators();
|
||||
// Update wood paneling indicators
|
||||
updateWoodPanelingIndicators();
|
||||
}
|
||||
```
|
||||
|
||||
**4. Keep existing updateThemeIndicators function (no change needed)**
|
||||
|
||||
**5. Add Alpine registration at bottom of file (after existing code):**
|
||||
```typescript
|
||||
import { Alpine } from "./alpine";
|
||||
|
||||
// Register composed function with Alpine
|
||||
Alpine.global("themeDropdown", {
|
||||
initialize: initializeThemeDropdown,
|
||||
updateIndicators: updateThemeIndicators,
|
||||
});
|
||||
|
||||
// Also keep the window export temporarily (remove in Phase 2)
|
||||
(window as any).initializeThemeDropdown = initializeThemeDropdown;
|
||||
(window as any).updateThemeIndicators = updateThemeIndicators;
|
||||
```
|
||||
|
||||
**Complete File After Conversion:**
|
||||
```typescript
|
||||
// web/src/themeDropdown.ts - Rewritten version
|
||||
|
||||
import { toggleThemeDropdown } from "./header";
|
||||
import { updateWoodPanelingIndicators } from "./woodPaneling";
|
||||
import { Alpine } from "./alpine";
|
||||
|
||||
// Composed theme dropdown initialization
|
||||
export function initializeThemeDropdown(): void {
|
||||
// Call original function from header.ts
|
||||
toggleThemeDropdown();
|
||||
// Then update theme indicators
|
||||
updateThemeIndicators();
|
||||
// Update wood paneling indicators
|
||||
updateWoodPanelingIndicators();
|
||||
}
|
||||
|
||||
// Export for Alpine
|
||||
export { updateThemeIndicators };
|
||||
// Update theme indicator elements (existing function, keep as-is)
|
||||
export function updateThemeIndicators(): void {
|
||||
const themeSelect = document.getElementById("theme-select") as HTMLSelectElement;
|
||||
const themeIndicator = document.getElementById("current-theme");
|
||||
|
||||
if (themeSelect && themeIndicator) {
|
||||
const theme = themeSelect.value as ThemeType;
|
||||
themeIndicator.textContent = theme.charAt(0).toUpperCase() + theme.slice(1);
|
||||
}
|
||||
|
||||
// Update wood paneling button
|
||||
const woodPanelingButton = document.getElementById("wood-paneling-button");
|
||||
if (woodPanelingButton) {
|
||||
const woodPaneling = localStorage.getItem("woodPaneling") || "none";
|
||||
woodPanelingButton.textContent = woodPaneling === "none" ? "🪵" : "✓";
|
||||
}
|
||||
}
|
||||
|
||||
// Register with Alpine for template access
|
||||
Alpine.global("themeDropdown", {
|
||||
initialize: initializeThemeDropdown,
|
||||
updateIndicators: updateThemeIndicators,
|
||||
});
|
||||
|
||||
// Temporary window exports (remove in Phase 2)
|
||||
(window as any).initializeThemeDropdown = initializeThemeDropdown;
|
||||
(window as any).updateThemeIndicators = updateThemeIndicators;
|
||||
```
|
||||
|
||||
**web/src/docs.ts** - Remove window globals
|
||||
**Changes Summary:**
|
||||
- ✅ Added 3 ES imports (toggleThemeDropdown, updateWoodPanelingIndicators, Alpine)
|
||||
- ✅ Deleted 8 lines of window read/override code
|
||||
- ✅ Created 1 new composed function (initializeThemeDropdown)
|
||||
- ✅ Added Alpine.global() registration (7 lines)
|
||||
- ✅ Kept window exports temporarily (2 lines, remove in Phase 2)
|
||||
|
||||
**Current (WRONG)**:
|
||||
---
|
||||
|
||||
#### 1.4.2: web/src/docs.ts - Window Export Conversion Only
|
||||
|
||||
**⚠️ IMPORTANT:** Search functionality changes (Lunr removal, backend API) are handled by a **separate DOCS_SEARCH_IMPLEMENTATION.md task**. This section ONLY covers ESBuild migration changes (window export → Alpine.global()).
|
||||
|
||||
---
|
||||
|
||||
**Current Issue (Line 91):**
|
||||
```typescript
|
||||
if (!(window as any).lunr) { ... }
|
||||
const idx = (window as any).lunrIndex;
|
||||
const doc = (window as any).docsData?.[result.ref];
|
||||
(window as any).toggleSidebar = toggleSidebar;
|
||||
```
|
||||
|
||||
**New Approach** (data already fetched via API):
|
||||
```typescript
|
||||
// Already using fetch in template inline JS (lines 228-285)
|
||||
// Just remove window checks, fetch API handles it
|
||||
This is the only window global that needs to be converted for the ESBuild migration.
|
||||
|
||||
// Keep only:
|
||||
**Note:** The search functionality (`performDocsSearch`, `initializeDocsSearch`, Lunr imports, etc.) will be addressed separately by the backend API search implementation (see DOCS_SEARCH_IMPLEMENTATION.md).
|
||||
|
||||
---
|
||||
|
||||
**Step-by-Step Conversion:**
|
||||
|
||||
**1. Add Alpine Import (after line 1):**
|
||||
```typescript
|
||||
// BEFORE:
|
||||
import * as lunr from "lunr";
|
||||
|
||||
// AFTER:
|
||||
import * as lunr from "lunr";
|
||||
import hljs from "highlight.js";
|
||||
import { Alpine } from "./alpine";
|
||||
```
|
||||
|
||||
// Export for Alpine
|
||||
export function initializeDocsSearch() { ... }
|
||||
export { toggleSidebar };
|
||||
**2. Remove Window Export and Add Alpine Registration (replace line 91):**
|
||||
```typescript
|
||||
// DELETE line 91:
|
||||
// (window as any).toggleSidebar = toggleSidebar;
|
||||
|
||||
// REPLACE with:
|
||||
// Register with Alpine for template access
|
||||
Alpine.global("docs", {
|
||||
toggleSidebar,
|
||||
initializeSearch: initializeDocsSearch,
|
||||
});
|
||||
```
|
||||
|
||||
### Step 1.5: Remove Window Exports from Consumer Files
|
||||
|
||||
After converting all reads to imports, remove window exports from consumer files (they don't need to expose to window anymore):
|
||||
|
||||
**web/src/dashboard.ts**
|
||||
**3. Add ES Module Export (at end of file):**
|
||||
```typescript
|
||||
// Remove these lines (if present):
|
||||
// (window as any).scrollCarousel = scrollCarousel;
|
||||
// (window as any).openDashboardSettings = openDashboardSettings;
|
||||
// ... etc
|
||||
export { toggleSidebar, initializeDocsSearch };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Complete Converted File (ESBuild changes only):**
|
||||
```typescript
|
||||
// web/src/docs.ts - ESBuild migration changes
|
||||
|
||||
import * as lunr from "lunr";
|
||||
import { Alpine } from "./alpine";
|
||||
|
||||
function toggleSidebar(): void {
|
||||
const sidebar = document.getElementById("docs-sidebar");
|
||||
const overlay = document.getElementById("docs-overlay");
|
||||
|
||||
if (sidebar && overlay) {
|
||||
sidebar.classList.toggle("translate-x-0");
|
||||
sidebar.classList.toggle("-translate-x-full");
|
||||
overlay.classList.toggle("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function initializeDocsSearch(): void {
|
||||
const searchInput = document.getElementById(
|
||||
"docs-search",
|
||||
) as HTMLInputElement;
|
||||
const searchResults = document.getElementById("docs-search-results");
|
||||
|
||||
if (!searchInput || !searchResults) return;
|
||||
|
||||
let docsSearchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
searchInput.addEventListener("input", () => {
|
||||
const query = searchInput.value.trim();
|
||||
|
||||
if (docsSearchTimeout) {
|
||||
clearTimeout(docsSearchTimeout);
|
||||
}
|
||||
|
||||
if (query.length < 2) {
|
||||
searchResults.innerHTML = "";
|
||||
searchResults.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
docsSearchTimeout = setTimeout(() => {
|
||||
performDocsSearch(query);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
function performDocsSearch(query: string): void {
|
||||
const searchResults = document.getElementById("docs-search-results");
|
||||
if (!searchResults) return;
|
||||
|
||||
try {
|
||||
const idx = lunr.Index.load(lunrIndexData);
|
||||
if (!idx) {
|
||||
searchResults.innerHTML =
|
||||
'<p class="p-2 text-sm" style="color: var(--text-secondary)">Search index not loaded</p>';
|
||||
searchResults.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
const results = idx.search(query);
|
||||
|
||||
if (results.length === 0) {
|
||||
searchResults.innerHTML =
|
||||
'<p class="p-2 text-sm" style="color: var(--text-secondary)">No results found</p>';
|
||||
} else {
|
||||
searchResults.innerHTML = results
|
||||
.slice(0, 10)
|
||||
.map((result: { ref: string }) => {
|
||||
const doc = docsData[result.ref];
|
||||
if (!doc) return "";
|
||||
|
||||
return `
|
||||
<a href="${result.ref}" class="block p-2 hover:bg-opacity-50 transition-colors" style="background-color: var(--bg-secondary)">
|
||||
<p class="font-medium text-sm" style="color: var(--text-primary)">${doc.title || result.ref}</p>
|
||||
${doc.section ? `<p class="text-xs" style="color: var(--text-secondary)">${doc.section}</p>` : ""}
|
||||
</a>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
searchResults.classList.remove("hidden");
|
||||
} catch (error) {
|
||||
console.error("Search error:", error);
|
||||
searchResults.innerHTML =
|
||||
'<p class="p-2 text-sm" style="color: var(--text-secondary)">Search error</p>';
|
||||
searchResults.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initializeDocsSearch();
|
||||
});
|
||||
|
||||
// Register with Alpine for template access
|
||||
Alpine.global("docs", {
|
||||
toggleSidebar,
|
||||
initializeSearch: initializeDocsSearch,
|
||||
});
|
||||
|
||||
// ES module exports
|
||||
export { toggleSidebar, initializeDocsSearch };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**ESBuild Migration Changes Only:**
|
||||
- ✅ Added 1 import (Alpine from "./alpine")
|
||||
- ✅ Removed 1 line (window export)
|
||||
- ✅ Added 7 lines (Alpine.global() registration)
|
||||
- ✅ Added 1 line (ES module export)
|
||||
|
||||
**Search Functionality:** Unchanged (handled by separate DOCS_SEARCH_IMPLEMENTATION.md task)
|
||||
|
||||
**Note:** The `lunrIndexData` and `docsData` undefined variables will be fixed when the backend API search is implemented (see DOCS_SEARCH_IMPLEMENTATION.md).
|
||||
|
||||
---
|
||||
|
||||
#### 1.4.3: No Other Files Need Function Wrapping Changes
|
||||
|
||||
**Confirmed Analysis:**
|
||||
- ✅ `header.ts` - Only exports functions, no wrapping
|
||||
- ✅ `woodPaneling.ts` - Only exports functions, no wrapping
|
||||
- ✅ All other consumer files - Only import/use functions, no wrapping
|
||||
|
||||
**Only 2 files affected:** themeDropdown.ts and docs.ts
|
||||
|
||||
---
|
||||
|
||||
#### 1.4.4: Verification Steps
|
||||
|
||||
After completing both file conversions:
|
||||
|
||||
```bash
|
||||
# Build TypeScript
|
||||
npm run build:ts
|
||||
|
||||
# Run application
|
||||
go run .
|
||||
|
||||
# Test in browser:
|
||||
# 1. Navigate to dashboard
|
||||
# 2. Click theme dropdown - should work and update indicators
|
||||
# 3. Navigate to /docs
|
||||
# 4. Search in docs - should work without console errors
|
||||
```
|
||||
|
||||
**Expected Results:**
|
||||
- ✅ Theme dropdown opens and updates indicators
|
||||
- ✅ Wood paneling indicators update correctly
|
||||
- ✅ Docs search works (lunr loaded via ES import)
|
||||
- ✅ No console errors about missing window.lunr
|
||||
- ✅ No console errors about missing window.lunrIndex
|
||||
- ✅ No console errors about missing window.docsData
|
||||
|
||||
**Common Issues:**
|
||||
- If theme dropdown doesn't open: Check if toggleThemeDropdown import is correct
|
||||
- If indicators don't update: Check if updateWoodPanelingIndicators is called
|
||||
- If docs search fails: Check if lunr is imported correctly (not via window)
|
||||
- If data missing: Check if fetch() API is working (not using window globals)
|
||||
|
||||
### Step 1.5: Remove Window Exports from Consumer Files (If Present)
|
||||
|
||||
After converting all reads to imports, check if consumer files have window exports to remove.
|
||||
|
||||
**Note:** Not all files have window exports. Some files (like dashboard.ts) use event delegation and don't export to window.
|
||||
|
||||
**Files WITH window exports to remove:**
|
||||
|
||||
**web/src/library.ts** (lines 690-704)
|
||||
```typescript
|
||||
// DELETE these window exports:
|
||||
(window as any).deleteLibrary = deleteLibrary;
|
||||
(window as any).showLibraryFolders = showLibraryFolders;
|
||||
(window as any).addLibraryFolder = addLibraryFolder;
|
||||
(window as any).removeLibraryFolder = removeLibraryFolder;
|
||||
(window as any).setLibraryVisibility = setLibraryVisibility;
|
||||
(window as any).loadUserVisibility = loadUserVisibility;
|
||||
(window as any).editLibrary = editLibrary;
|
||||
(window as any).handleCreateLibrarySubmit = handleCreateLibrarySubmit;
|
||||
(window as any).showFolderBrowser = showFolderBrowser;
|
||||
(window as any).navigateFolderBrowser = navigateFolderBrowser;
|
||||
(window as any).selectBrowseFolder = selectBrowseFolder;
|
||||
(window as any).hideFolderBrowser = hideFolderBrowser;
|
||||
(window as any).showDeleteModal = showDeleteModal;
|
||||
(window as any).hideDeleteModal = hideDeleteModal;
|
||||
(window as any).confirmDeleteLibrary = confirmDeleteLibrary;
|
||||
```
|
||||
|
||||
**web/src/collections.ts** (multiple locations: lines 204-208, 243, 315-317, 472-476, 912-923)
|
||||
```typescript
|
||||
// DELETE all window exports (26 functions total)
|
||||
// Examples:
|
||||
(window as any).loadCollections = loadCollections;
|
||||
(window as any).loadCollectionRules = loadCollectionRules;
|
||||
// ... etc (all 26 exports)
|
||||
```
|
||||
|
||||
**web/src/bookshelf.ts** (lines 127-130)
|
||||
```typescript
|
||||
// DELETE these window exports:
|
||||
(window as any).selectLibrary = selectLibrary;
|
||||
(window as any).loadBookshelf = loadBookshelf;
|
||||
(window as any).selectBook = selectBook;
|
||||
(window as any).changePage = changePage;
|
||||
```
|
||||
|
||||
**web/src/api-explorer.ts** (lines 186-189)
|
||||
```typescript
|
||||
// DELETE these window exports:
|
||||
(window as any).sendApiRequest = sendApiRequest;
|
||||
(window as any).loadFromHistory = loadFromHistory;
|
||||
(window as any).copyCurl = copyCurl;
|
||||
(window as any).formatJson = formatJson;
|
||||
```
|
||||
|
||||
**Files WITHOUT window exports (already clean):**
|
||||
- ✅ **dashboard.ts** - Uses event delegation, no window exports
|
||||
- ✅ **admin.ts** - Check if it has exports
|
||||
- ✅ **queue.ts** - Check if it has exports
|
||||
- ✅ **conflicts.ts** - Check if it has exports
|
||||
- ✅ **analytics.ts** - Check if it has exports
|
||||
- ✅ **device-management.ts** - Check if it has exports
|
||||
- ✅ **linking.ts** - Check if it has exports
|
||||
- ✅ **custom-section-builder.ts** - Check if it has exports
|
||||
|
||||
**Verification:**
|
||||
For each file, search for `(window as any).` at the end of the file. If present, remove those lines.
|
||||
|
||||
**Only keep Alpine.global()** for functions that templates call directly.
|
||||
|
||||
### Step 1.6: Verify Build and Test
|
||||
|
||||
Reference in New Issue
Block a user