Skip to content

Commit 2f6ffe5

Browse files
committed
perf: lazy-load file publish manifest to speed file:// navigation
1 parent 91edc6d commit 2f6ffe5

File tree

1 file changed

+58
-41
lines changed

1 file changed

+58
-41
lines changed

src/protocols/file-handler.js

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from "path";
33
import mime from "mime-types";
44
import { pathToFileURL } from "url";
55

6-
function generateDirectoryListing(dirPath, entries, allFilesForPublishing) {
6+
function generateDirectoryListing(dirPath, entries) {
77
const parentPath = path.dirname(dirPath);
88
const parentDirName = path.basename(parentPath) || 'Parent Directory';
99

@@ -92,8 +92,26 @@ function generateDirectoryListing(dirPath, entries, allFilesForPublishing) {
9292
<div id="result"></div>
9393
9494
<script>
95-
// This list is now pre-compiled by the main process with all recursive files and correct paths!
96-
const allFiles = ${JSON.stringify(allFilesForPublishing)};
95+
let manifestCache = null;
96+
97+
async function loadManifest() {
98+
if (manifestCache) return manifestCache;
99+
100+
const manifestUrl = new URL(window.location.href);
101+
manifestUrl.searchParams.set('__publishManifest', '1');
102+
103+
const response = await fetch(manifestUrl.toString(), {
104+
headers: { 'Accept': 'application/json' }
105+
});
106+
107+
if (!response.ok) {
108+
throw new Error('Failed to load directory manifest');
109+
}
110+
111+
const data = await response.json();
112+
manifestCache = data.files || [];
113+
return manifestCache;
114+
}
97115
98116
async function publishDirectory() {
99117
const protocol = document.getElementById('protocolSelect').value;
@@ -106,20 +124,21 @@ function generateDirectoryListing(dirPath, entries, allFilesForPublishing) {
106124
publishBtn.disabled = true;
107125
108126
try {
109-
if (allFiles.length === 0) {
127+
const files = await loadManifest();
128+
129+
if (files.length === 0) {
110130
throw new Error('No files found in this directory or its subdirectories.');
111131
}
112-
132+
113133
if (protocol === 'hyper') {
114-
// Hypercore upload
115134
const hyperdriveUrl = await generateHyperdriveKey('directory-' + Date.now());
116135
console.log('Hyper base URL:', hyperdriveUrl);
117-
118-
for (const fileEntry of allFiles) {
136+
137+
for (const fileEntry of files) {
119138
const url = hyperdriveUrl + encodeURIComponent(fileEntry.relativePath);
120139
console.log('Uploading', fileEntry.relativePath, 'to', url);
121-
122-
const fileResponse = await fetch('file://' + fileEntry.path);
140+
141+
const fileResponse = await fetch(fileEntry.fileUrl);
123142
const blob = await fileResponse.blob();
124143
125144
const uploadResponse = await fetch(url, {
@@ -143,26 +162,20 @@ function generateDirectoryListing(dirPath, entries, allFilesForPublishing) {
143162
result.appendChild(hyperAnchor);
144163
145164
} else {
146-
// IPFS upload - exactly like upload.html
147165
const formData = new FormData();
148-
149-
for (const fileEntry of allFiles) {
150-
console.log('Processing file:', fileEntry.relativePath, 'size:', fileEntry.size);
151-
152-
// Convert base64 back to blob
153-
const binaryString = atob(fileEntry.base64);
154-
const bytes = new Uint8Array(binaryString.length);
155-
for (let i = 0; i < binaryString.length; i++) {
156-
bytes[i] = binaryString.charCodeAt(i);
166+
167+
for (const fileEntry of files) {
168+
console.log('Processing file:', fileEntry.relativePath);
169+
170+
const response = await fetch(fileEntry.fileUrl);
171+
if (!response.ok) {
172+
throw new Error('Failed to read ' + fileEntry.relativePath);
157173
}
158-
const blob = new Blob([bytes], { type: 'application/octet-stream' });
159-
160-
// Use the relative path as the file name to preserve directory structure
174+
const blob = await response.blob();
175+
161176
const fileName = fileEntry.relativePath || fileEntry.name;
162-
console.log('Creating File with name:', fileName);
163-
const file = new File([blob], fileName, { type: 'application/octet-stream' });
164-
165-
// The third argument to append() is what IPFS uses as the filename.
177+
const file = new File([blob], fileName, { type: blob.type || 'application/octet-stream' });
178+
166179
formData.append('file', file, fileName);
167180
}
168181
@@ -253,19 +266,17 @@ export async function createHandler() {
253266
const subFiles = await getFilesRecursive(fullPath, relativePath);
254267
allFiles.push(...subFiles);
255268
} else {
256-
// Read the file content and convert to base64 for embedding
257269
try {
258-
const fileContent = await fs.readFile(fullPath);
259-
const base64Content = fileContent.toString('base64');
270+
const stat = await fs.stat(fullPath);
260271
allFiles.push({
261272
name: entry.name,
262273
path: fullPath,
263-
relativePath: relativePath,
264-
base64: base64Content,
265-
size: fileContent.length
274+
relativePath,
275+
size: stat.size,
276+
fileUrl: pathToFileURL(fullPath).href
266277
});
267278
} catch (err) {
268-
console.error('Could not read file:', fullPath, err);
279+
console.error('Could not stat file:', fullPath, err);
269280
}
270281
}
271282
}
@@ -288,6 +299,18 @@ export async function createHandler() {
288299
console.log('Path stats:', filePath, 'isDirectory:', stats.isDirectory(), 'isFile:', stats.isFile());
289300

290301
if (stats.isDirectory()) {
302+
// Handle manifest requests first (used for publishing)
303+
if (url.searchParams.get('__publishManifest') === '1') {
304+
const manifest = await getFilesRecursive(filePath, '');
305+
return new Response(JSON.stringify({ files: manifest }), {
306+
status: 200,
307+
headers: {
308+
'Content-Type': 'application/json; charset=utf-8',
309+
'Cache-Control': 'no-cache'
310+
}
311+
});
312+
}
313+
291314
// For directories, read entries for the current level to display
292315
const dirEntries = await fs.readdir(filePath);
293316
const entryStats = await Promise.all(
@@ -309,13 +332,7 @@ export async function createHandler() {
309332

310333
const validEntries = entryStats.filter(e => e !== null);
311334

312-
// **BEFORE** generating the HTML, read the *entire* directory recursively.
313-
// This gives the script embedded in the HTML the full list of files to publish.
314-
// Don't use the directory name as base path - just use empty string
315-
// so paths are relative to the current directory
316-
const allFilesForPublishing = await getFilesRecursive(filePath, '');
317-
318-
const html = generateDirectoryListing(filePath, validEntries, allFilesForPublishing);
335+
const html = generateDirectoryListing(filePath, validEntries);
319336

320337
return new Response(html, {
321338
status: 200,

0 commit comments

Comments
 (0)