Electron应用中如何递归列出选中文件夹内的所有文件?
Hey, I’ve dealt with this exact problem in Electron before! The issue is that while regular browsers return all nested files when using the webkitdirectory attribute, Electron’s Chromium implementation only gives you the top-level folders in the event.target.files list. To get all the files inside, we need to use Electron’s native file system capabilities instead of relying solely on the browser’s File API.
Why Your Current Code Isn’t Working
In standard browsers, the webkitdirectory attribute tells the browser to recursively collect all files in the selected folder(s) and return them in the files array. But in Electron, this behavior is restricted—you only get the top-level folders, not their contents. This is likely due to Electron’s security sandboxing and how it handles native file system access.
Solution 1: Direct File System Access in Renderer (Quick, Less Secure)
If you’re working on a development build or don’t mind relaxing some security settings temporarily, you can use Node.js’s fs module directly in the renderer process to traverse folders recursively.
Step 1: Update BrowserWindow Settings
First, enable Node.js integration in your main process (main.js):
const { app, BrowserWindow } = require('electron'); const path = require('path'); function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, contextIsolation: false // Required for renderer to access Node modules } }); mainWindow.loadFile('index.html'); } // Rest of your main process code...
Step 2: Modify Renderer Code (index.js)
Replace your existing listener with this recursive traversal code:
const fs = require('fs').promises; const path = require('path'); document.getElementById("filepicker").addEventListener("change", async function(event) { const output = document.getElementById("listing"); output.innerHTML = ''; // Clear previous results // Recursive function to traverse folders async function traverseFolder(dirPath, rootDir) { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { await traverseFolder(fullPath, rootDir); // Recurse into subfolders } else { // Calculate path relative to the selected root folder const relativePath = path.relative(rootDir, fullPath); const listItem = document.createElement("li"); listItem.textContent = relativePath; output.appendChild(listItem); } } } // Process each selected folder for (const file of event.target.files) { if (!file.type) { // Check if it's a folder (Electron sets folder type to empty string) await traverseFolder(file.path, file.path); } else { // Handle single file selection const listItem = document.createElement("li"); listItem.textContent = file.name; output.appendChild(listItem); } } }, false);
Solution 2: IPC Communication (Secure, Production-Ready)
For production apps, it’s best practice to keep file system operations in the main process (to avoid exposing Node.js APIs to the renderer). We’ll use Electron’s IPC (Inter-Process Communication) to send folder paths from the renderer to the main process, which will handle the traversal and send back the results.
Step 1: Update Main Process (main.js)
Add an IPC handler to handle folder traversal requests:
const { app, BrowserWindow, ipcMain } = require('electron'); const fs = require('fs').promises; const path = require('path'); function createWindow() { const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { contextIsolation: true, // Keep security enabled preload: path.join(__dirname, 'preload.js') // Expose safe APIs to renderer } }); mainWindow.loadFile('index.html'); } // IPC handler to traverse folders ipcMain.handle('traverse-folder', async (_, folderPath) => { const fileList = []; async function traverse(dir, rootDir) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { await traverse(fullPath, rootDir); } else { fileList.push(path.relative(rootDir, fullPath)); } } } await traverse(folderPath, folderPath); return fileList; }); // Rest of your main process code...
Step 2: Create Preload Script (preload.js)
This script safely exposes the IPC method to the renderer without exposing full Node.js access:
const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { traverseFolder: (folderPath) => ipcRenderer.invoke('traverse-folder', folderPath) });
Step 3: Update Renderer Code (index.js)
Use the exposed API to request folder traversal from the main process:
document.getElementById("filepicker").addEventListener("change", async function(event) { const output = document.getElementById("listing"); output.innerHTML = ''; for (const file of event.target.files) { if (!file.type) { // Request main process to traverse the folder const fileList = await window.electronAPI.traverseFolder(file.path); // Add all files to the list fileList.forEach(relativePath => { const listItem = document.createElement("li"); listItem.textContent = relativePath; output.appendChild(listItem); }); } else { const listItem = document.createElement("li"); listItem.textContent = file.name; output.appendChild(listItem); } } }, false);
Key Notes
- Always prefer the IPC approach for production apps—it follows Electron’s security best practices by keeping sensitive file system operations in the main process.
- When checking if a
Fileobject is a folder in Electron, look for an emptytypeproperty (since folders don’t have a MIME type).
内容的提问来源于stack exchange,提问作者Akash Srinivasan




