AgentSkillsCN

expo-file-system-class-api-migration

解决在升级至 Expo SDK 52+ 时,expo-file-system 迁移错误的问题。适用场景:(1) TypeScript 错误提示“Property 'cacheDirectory' does not exist on type”;(2) “Module has no exported member 'EncodingType'” 错误;(3) “Property 'deleteAsync' does not exist” 或类似 FileSystem 函数错误;(4) file.info 返回的是一个函数,而非文件信息对象;(5) 升级 Expo SDK 与 expo-file-system 代码后,原有功能出现中断。此技能涵盖从基于旧版函数的 API(FileSystem.deleteAsync、getInfoAsync)迁移到全新的基于类的 API(File、Directory、Paths 类)。

SKILL.md
--- frontmatter
name: expo-file-system-class-api-migration
description: |
  Fix for expo-file-system migration errors when upgrading to Expo SDK 52+. Use when:
  (1) TypeScript error "Property 'cacheDirectory' does not exist on type",
  (2) "Module has no exported member 'EncodingType'" error,
  (3) "Property 'deleteAsync' does not exist" or similar FileSystem function errors,
  (4) file.info returns a function instead of file info object,
  (5) Upgrading Expo SDK and expo-file-system code breaks.
  Covers migration from legacy function-based API (FileSystem.deleteAsync, getInfoAsync)
  to new class-based API (File, Directory, Paths classes).
author: Claude Code
version: 1.0.0
date: 2025-01-23

Expo File System Class API Migration

Problem

After upgrading to Expo SDK 52+ (expo-file-system 18+), the legacy function-based API no longer works. Code using FileSystem.deleteAsync(), FileSystem.getInfoAsync(), FileSystem.cacheDirectory, etc. will fail with TypeScript errors or runtime issues.

Context / Trigger Conditions

You'll encounter this when:

  • Upgrading Expo SDK from 51 or earlier to 52+
  • TypeScript errors like:
    • Property 'cacheDirectory' does not exist on type 'typeof import("expo-file-system")'
    • Module '"expo-file-system"' has no exported member 'EncodingType'
    • Property 'deleteAsync' does not exist
    • Property 'getInfoAsync' does not exist
  • Runtime issues where file.info returns a function instead of file metadata
  • Any code importing import * as FileSystem from 'expo-file-system'

Solution

1. Change Import Style

typescript
// OLD (legacy API)
import * as FileSystem from 'expo-file-system';

// NEW (class-based API)
import { File, Directory, Paths } from 'expo-file-system';

2. Replace Common Operations

Legacy APINew Class-Based API
FileSystem.cacheDirectoryPaths.cache
FileSystem.documentDirectoryPaths.document
FileSystem.deleteAsync(uri, { idempotent: true })new File(uri).delete()
FileSystem.getInfoAsync(uri)new File(uri).exists (for existence check)
FileSystem.writeAsStringAsync(uri, content, { encoding: 'base64' })new File(path, name).write(content, { encoding: 'base64' })
FileSystem.readAsStringAsync(uri)new File(uri).text()

3. File Creation Pattern

typescript
// OLD
const filePath = `${FileSystem.cacheDirectory}voice_${Date.now()}.mp3`;
await FileSystem.writeAsStringAsync(filePath, base64Data, {
  encoding: FileSystem.EncodingType.Base64
});

// NEW
const fileName = `voice_${Date.now()}.mp3`;
const file = new File(Paths.cache, fileName);
await file.write(base64Data, { encoding: 'base64' });
const uri = file.uri;

4. File Deletion Pattern

typescript
// OLD
await FileSystem.deleteAsync(uri, { idempotent: true });

// NEW
try {
  const file = new File(uri);
  if (file.exists) {
    await file.delete();
  }
} catch {
  // Ignore delete errors (equivalent to idempotent)
}

5. File Info Pattern

typescript
// OLD
const info = await FileSystem.getInfoAsync(uri);
if (info.exists && info.modificationTime) {
  // use info.modificationTime
}

// NEW - Note: info is now a METHOD, not a property
const file = new File(uri);
if (file.exists) {
  // For modification time, use filename timestamps or other approach
  // The new API structure differs for file metadata
}

6. Directory Listing

typescript
// OLD
const files = await FileSystem.readDirectoryAsync(cacheDir);

// NEW
const cacheDir = Paths.cache;
const contents = cacheDir.list(); // Returns array of File/Directory objects
for (const item of contents) {
  if (item instanceof File) {
    console.log(item.name, item.uri);
  }
}

Verification

After migration:

  1. Run npx tsc --noEmit - should pass with no expo-file-system errors
  2. Test file operations in the app to ensure they work correctly

Example

Complete before/after for a voice message cache cleanup:

typescript
// BEFORE (Legacy API)
import * as FileSystem from 'expo-file-system';

export async function cleanupAudioCache(maxAgeMs: number): Promise<void> {
  const cacheDir = FileSystem.cacheDirectory;
  if (!cacheDir) return;

  const files = await FileSystem.readDirectoryAsync(cacheDir);
  const now = Date.now();

  for (const fileName of files) {
    if (fileName.startsWith('voice_') && fileName.endsWith('.mp3')) {
      const filePath = `${cacheDir}${fileName}`;
      const info = await FileSystem.getInfoAsync(filePath);
      if (info.exists && info.modificationTime) {
        const age = now - info.modificationTime * 1000;
        if (age > maxAgeMs) {
          await FileSystem.deleteAsync(filePath, { idempotent: true });
        }
      }
    }
  }
}

// AFTER (Class-Based API)
import { File, Paths } from 'expo-file-system';

export async function cleanupAudioCache(maxAgeMs: number): Promise<void> {
  const cacheDir = Paths.cache;
  const contents = cacheDir.list();
  const now = Date.now();

  for (const item of contents) {
    if (item instanceof File &&
        item.name.startsWith('voice_') &&
        item.name.endsWith('.mp3')) {
      // Extract timestamp from filename (voice_{timestamp}.mp3)
      const match = item.name.match(/voice_(\d+)\.mp3/);
      if (match) {
        const timestamp = parseInt(match[1], 10);
        const age = now - timestamp;
        if (age > maxAgeMs) {
          await item.delete();
        }
      }
    }
  }
}

Notes

  • The new API uses classes (File, Directory) rather than functions
  • Paths object provides standard directories (cache, document, etc.)
  • File metadata access has changed - info is now a method, not a property
  • The new API is more object-oriented and TypeScript-friendly
  • Some operations that were async are now sync (like exists, list())

References