Lo standard del file system introduce un file system privato di origine (OPFS) come endpoint di archiviazione privato per l'origine della pagina e non visibile all'utente che fornisce l'accesso facoltativo a un tipo speciale di file altamente ottimizzato per le prestazioni.
Supporto browser
Il file system privato di origine è supportato dai browser moderni ed è standardizzato dal Web Hypertext Application Technology Working Group (WHATWG) nello standard File System Living.
Motivazione
Quando pensi ai file sul computer, probabilmente pensi a una gerarchia di file: file organizzati in cartelle che puoi esplorare con Esplora file del sistema operativo. Ad esempio, su Windows, per un utente di nome Tom, il suo elenco di cose da fare potrebbe trovarsi in C:\Users\Tom\Documents\ToDo.txt. In questo esempio, ToDo.txt è il nome del file e Users, Tom e Documents sono nomi di cartelle. `C:` su Windows rappresenta la directory principale dell'unità.
Modo tipico di lavorare con i file sul web
Per modificare l'elenco di cose da fare in un'applicazione web, il flusso è il seguente:
- L'utente carica il file su un server o lo apre sul client con
<input type="file">. - L'utente apporta le modifiche, quindi scarica il file risultante con un
inserito
<a download="ToDo.txt>che faiclick()con JavaScript. - Per aprire le cartelle, utilizzi un attributo speciale in
<input type="file" webkitdirectory>, che, nonostante il nome proprietario, ha un supporto browser praticamente universale.
Modo moderno di lavorare con i file sul web
Questo flusso non è rappresentativo di come gli utenti pensano di modificare i file e significa che gli utenti finiscono per avere copie scaricate dei file di input. Pertanto, l'API File System Access ha introdotto tre metodi di selezione:
Consentono un flusso come segue:
- Apri
ToDo.txtconshowOpenFilePicker()e ottieni unFileSystemFileHandleoggetto. - Dall'
FileSystemFileHandleoggetto, ottieni unFilechiamando il metodogetFile()dell'handle del file. - Modifica il file, quindi chiama
requestPermission({mode: 'readwrite'})sull'handle. - Se l'utente accetta la richiesta di autorizzazione, salva le modifiche nel file originale.
- In alternativa, chiama
showSaveFilePicker()e lascia che l'utente scelga un nuovo file. (Se l'utente sceglie un file aperto in precedenza, i relativi contenuti verranno sovrascritti.) Per i salvataggi ripetuti, puoi conservare l'handle del file, in modo da non dover mostrare di nuovo la finestra di dialogo di salvataggio del file.
Limitazioni relative all'utilizzo dei file sul web
I file e le cartelle accessibili con questi metodi si trovano in quello che può essere definito il file system visibile all'utente. I file salvati dal web e i file eseguibili in particolare sono contrassegnati con il segno del web, quindi il sistema operativo può mostrare un avviso aggiuntivo prima dell'esecuzione di un file potenzialmente pericoloso. Come funzionalità di sicurezza aggiuntiva, i file ottenuti dal web sono protetti anche da Navigazione sicura, che, per semplicità e nel contesto di questo documento, puoi considerare come una scansione antivirus basata sul cloud. Quando scrivi dati in un file utilizzando l'API File System Access, le scritture non vengono eseguite in loco, ma utilizzano un file temporaneo. Il file stesso non viene modificato a meno che non superi tutti questi controlli di sicurezza.
Questo lavoro rende le operazioni sui file relativamente lente, nonostante i miglioramenti applicati
ove possibile, ad esempio su macOS.
Tuttavia, ogni write()
chiamata è autonoma, quindi sotto il cofano apre il file, cerca l'offset specificato
e infine scrive i dati.
I file come base dell'elaborazione
Allo stesso tempo, i file sono un modo eccellente per registrare i dati. Ad esempio, SQLite archivia interi database in un singolo file. Un altro esempio sono le mipmap utilizzate nell'elaborazione delle immagini. Le mipmap sono sequenze di immagini precalcolate e ottimizzate, ognuna delle quali è una rappresentazione a risoluzione progressivamente inferiore della precedente, il che rende più veloci molte operazioni come lo zoom. Quindi, in che modo le applicazioni web possono ottenere i vantaggi dei file, ma senza i costi di prestazioni dell'elaborazione dei file basata sul web? La risposta è il file system privato di origine.
File system visibile all'utente e file system privato di origine
A differenza del file system visibile all'utente esplorato utilizzando Esplora file del sistema operativo, con file e cartelle che puoi leggere, scrivere, spostare e rinominare, il file system privato di origine non è destinato a essere visto dagli utenti. I file e le cartelle
nel file system privato di origine, come suggerisce il nome, sono privati e, più
concretamente, privati per l'origine
di un sito. Scopri l'origine di una pagina digitando
location.origin
nella console DevTools. Ad esempio, l'origine della pagina
https://developer.chrome.com/articles/ è https://developer.chrome.com.
Scopri di più sulla teoria delle origini in
Understanding "same-site" and "same-origin".
Tutte le pagine che condividono la stessa origine possono visualizzare gli stessi dati del file system privato di origine, quindi https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/ può visualizzare gli stessi dettagli dell'esempio precedente. Ogni origine ha il proprio
file system privato di origine indipendente, il che significa che il file system privato di origine
di https://developer.chrome.com è completamente distinto da quello
di, ad esempio, https://web.dev. Su Windows, la directory principale del file system visibile all'utente è C:\\.
L'equivalente per il file system privato di origine è una directory principale inizialmente vuota per origine a cui si accede chiamando il metodo asincrono
navigator.storage.getDirectory().
Per un confronto tra il file system visibile all'utente e il file system privato di origine, consulta il seguente diagramma. Il diagramma mostra che, a parte la directory principale, tutto il resto è concettualmente lo stesso, con una gerarchia di file e cartelle da organizzare e disporre in base alle esigenze di dati e archiviazione.
navigator.storage.getDirectory.Specifiche del file system privato di origine
Come altri meccanismi di archiviazione nel browser (ad esempio, localStorage o IndexedDB), il file system privato di origine è soggetto a limitazioni di quota del browser. Quando un utente cancella tutti i dati di navigazione o tutti i dati del sito, viene eliminato anche il file system privato di origine.
Chiama navigator.storage.estimate()
e nell'oggetto di risposta risultante visualizza la
usage
voce per vedere la quantità di spazio di archiviazione già utilizzata dall'app, suddivisa per
meccanismo di archiviazione nell'oggetto usageDetails, dove devi esaminare in modo specifico la voce fileSystem. Poiché il file system privato di origine non è visibile all'utente, non vengono visualizzati prompt di autorizzazione né controlli di Navigazione sicura.
Ottenere l'accesso alla directory principale
Per accedere alla directory principale, esegui il seguente comando. Viene visualizzato un handle di directory vuoto, più precisamente un
FileSystemDirectoryHandle.
const opfsRoot = await navigator.storage.getDirectory();
// A FileSystemDirectoryHandle whose type is "directory"
// and whose name is "".
console.log(opfsRoot);
Thread principale o worker web
Esistono due modi per utilizzare il file system privato di origine: sul thread principale o in un worker web. I worker web non possono bloccare il thread principale, il che significa che in questo contesto le API possono essere sincrone, un pattern generalmente non consentito sul thread principale. Le API sincrone possono essere più veloci perché evitano di dover gestire le promesse e le operazioni sui file sono in genere sincrone in linguaggi come C che possono essere compilati in WebAssembly.
// This is synchronous C code.
FILE *f;
f = fopen("example.txt", "w+");
fputs("Some text\n", f);
fclose(f);
Se hai bisogno delle operazioni sui file più veloci possibili o se utilizzi WebAssembly, vai a Utilizzare il file system privato di origine in un worker web.
Utilizzare il file system privato di origine sul thread principale
Creare nuovi file e cartelle
Dopo aver creato una cartella principale, crea file e cartelle utilizzando i metodi
getFileHandle()
e getDirectoryHandle()
rispettivamente. Se passi
{create: true},
il file o la cartella verrà creato se non esiste. Crea una gerarchia di file chiamando queste funzioni utilizzando come punto di partenza una directory appena creata.
const fileHandle = await opfsRoot
.getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
.getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
.getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
.getDirectoryHandle('my first nested folder', {create: true});

Accedere a file e cartelle esistenti
Se conosci il nome, accedi ai file e alle cartelle creati in precedenza chiamando i metodi getFileHandle() o getDirectoryHandle(), passando il nome del file o della cartella.
const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
.getDirectoryHandle('my first folder');
Ottenere il file associato a un handle di file per la lettura
Un FileSystemFileHandle rappresenta un file nel file system. Per ottenere l'
associato File, utilizza il getFile()
metodo. Un oggetto File è un tipo specifico di
Blob e può essere utilizzato in
qualsiasi contesto in cui può essere utilizzato un Blob.
In particolare, FileReader,
URL.createObjectURL(),
createImageBitmap(),
e XMLHttpRequest.send()
accettano sia Blobs sia Files. L'ottenimento di un File da un
FileSystemFileHandle "libera" i dati, in modo che tu possa accedervi e renderli
disponibili al file system visibile all'utente.
const file = await fileHandle.getFile();
console.log(await file.text());
Scrivere in un file tramite streaming
Trasmetti i dati in streaming in un file chiamando
createWritable()
che crea un FileSystemWritableFileStream
in cui poi write()
i contenuti. Alla fine, devi
close()
lo stream.
const contents = 'Some text';
// Get a writable stream.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the stream, which persists the contents.
await writable.close();
Eliminare file e cartelle
Elimina file e cartelle chiamando il metodo
remove()
specifico dell'handle del file o della directory. Per eliminare una cartella, incluse tutte le sottocartelle, passa l'
{recursive: true}
opzione.
await fileHandle.remove();
await directoryHandle.remove({recursive: true});
In alternativa, se conosci il nome del file o della cartella da eliminare in una
directory, utilizza il removeEntry()
metodo.
directoryHandle.removeEntry('my first nested file');
Spostare e rinominare file e cartelle
Rinomina e sposta file e cartelle utilizzando il
move() metodo. Lo spostamento e la ridenominazione possono avvenire insieme o in isolamento.
// Rename a file.
await fileHandle.move('my first renamed file');
// Move a file to another directory.
await fileHandle.move(nestedDirectoryHandle);
// Move a file to another directory and rename it.
await fileHandle
.move(nestedDirectoryHandle, 'my first renamed and now nested file');
Risolvere il percorso di un file o di una cartella
Per scoprire dove si trova un determinato file o cartella rispetto a una directory di riferimento, utilizza il resolve()
metodo, passandogli un FileSystemHandle come argomento. Per ottenere il percorso completo di un file o di una cartella nel file system privato di origine, utilizza la directory principale come directory di riferimento ottenuta utilizzando navigator.storage.getDirectory().
const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
// `relativePath` is `['my first folder', 'my first nested folder']`.
Verificare se due handle di file o cartelle puntano allo stesso file o alla stessa cartella
A volte hai due handle e non sai se puntano allo stesso file o alla stessa cartella. Per verificare se è così, utilizza il
isSameEntry()
metodo.
fileHandle.isSameEntry(nestedFileHandle);
// Returns `false`.
Elencare i contenuti di una cartella
FileSystemDirectoryHandle è un
iteratore asincrono
su cui puoi eseguire l'iterazione con un
for await... of
loop. Come iteratore asincrono, supporta anche i
entries(),
metodi values(),
e keys(), tra cui puoi scegliere a seconda delle informazioni di cui hai bisogno:
for await (let [name, handle] of directoryHandle) {}
for await (let [name, handle] of directoryHandle.entries()) {}
for await (let handle of directoryHandle.values()) {}
for await (let name of directoryHandle.keys()) {}
Elencare in modo ricorsivo i contenuti di una cartella e di tutte le sottocartelle
È facile sbagliare quando si utilizzano loop e funzioni asincroni abbinati alla ricorsione. La seguente funzione può fungere da punto di partenza per elencare i contenuti di una cartella e di tutte le relative sottocartelle, inclusi tutti i file e le relative dimensioni. Puoi semplificare la funzione se non hai bisogno delle dimensioni dei file, non inserendo la handle.getFile()
promessa in directoryEntryPromises.push, ma il handle direttamente.
const getDirectoryEntriesRecursive = async (
directoryHandle,
relativePath = '.',
) => {
const fileHandles = [];
const directoryHandles = [];
const entries = {};
// Get an iterator of the files and folders in the directory.
const directoryIterator = directoryHandle.values();
const directoryEntryPromises = [];
for await (const handle of directoryIterator) {
const nestedPath = `${relativePath}/${handle.name}`;
if (handle.kind === 'file') {
fileHandles.push({ handle, nestedPath });
directoryEntryPromises.push(
handle.getFile().then((file) => {
return {
name: handle.name,
kind: handle.kind,
size: file.size,
type: file.type,
lastModified: file.lastModified,
relativePath: nestedPath,
handle
};
}),
);
} else if (handle.kind === 'directory') {
directoryHandles.push({ handle, nestedPath });
directoryEntryPromises.push(
(async () => {
return {
name: handle.name,
kind: handle.kind,
relativePath: nestedPath,
entries:
await getDirectoryEntriesRecursive(handle, nestedPath),
handle,
};
})(),
);
}
}
const directoryEntries = await Promise.all(directoryEntryPromises);
directoryEntries.forEach((directoryEntry) => {
entries[directoryEntry.name] = directoryEntry;
});
return entries;
};
Utilizzare il file system privato di origine in un worker web
Come indicato in precedenza, i worker web non possono bloccare il thread principale, motivo per cui in questo contesto sono consentiti i metodi sincroni.
Ottenere un handle di accesso sincrono
Il punto di ingresso per le operazioni sui file più veloci possibili è un
FileSystemSyncAccessHandle,
ottenuto da un normale
FileSystemFileHandle
chiamando createSyncAccessHandle().
const fileHandle = await opfsRoot
.getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
Metodi di file sincroni in loco
Una volta che hai un handle di accesso sincrono, puoi accedere a metodi di file in loco veloci che sono tutti sincroni.
getSize(): restituisce le dimensioni del file in byte.write(): scrive il contenuto di un buffer nel file, facoltativamente a un determinato offset, e restituisce il numero di byte scritti. Il controllo del numero di byte scritti restituiti consente ai chiamanti di rilevare e gestire errori e scritture parziali.read(): legge i contenuti del file in un buffer, facoltativamente a un determinato offset.truncate(): ridimensiona il file in base alle dimensioni specificate.flush(): garantisce che i contenuti del file contengano tutte le modifiche apportate tramitewrite().close(): chiude l'handle di accesso.
Ecco un esempio che utilizza ogni metodo.
const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle('fast', {create: true});
const accessHandle = await fileHandle.createSyncAccessHandle();
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode('Some text');
// Write the content at the beginning of the file.
accessHandle.write(content, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();
// Encode more content to write to the file.
const moreContent = textEncoder.encode('More content');
// Write the content at the end of the file.
accessHandle.write(moreContent, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();
// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));
// Read the entire file into the data view.
accessHandle.read(dataView);
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));
// Read starting at offset 9 into the data view.
accessHandle.read(dataView, {at: 9});
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));
// Truncate the file after 4 bytes.
accessHandle.truncate(4);
Copiare un file dal file system privato di origine al file system visibile all'utente
Come accennato in precedenza, non è possibile spostare i file dal file system privato di origine al file system visibile all'utente, ma puoi copiarli. Poiché showSaveFilePicker() è esposto solo sul thread principale, ma non nel thread del worker, assicurati di eseguire il codice lì.
// On the main thread, not in the Worker. This assumes
// `fileHandle` is the `FileSystemFileHandle` you obtained
// the `FileSystemSyncAccessHandle` from in the Worker
// thread. Be sure to close the file in the Worker thread first.
const fileHandle = await opfsRoot.getFileHandle('fast');
try {
// Obtain a file handle to a new file in the user-visible file system
// with the same name as the file in the origin private file system.
const saveHandle = await showSaveFilePicker({
suggestedName: fileHandle.name || ''
});
const writable = await saveHandle.createWritable();
await writable.write(await fileHandle.getFile());
await writable.close();
} catch (err) {
console.error(err.name, err.message);
}
Eseguire il debug del file system privato di origine
Finché non viene aggiunto il supporto integrato di DevTools (vedi crbug/1284595), utilizza l'estensione di Chrome OPFS Explorer per eseguire il debug del file system privato di origine. Lo screenshot da la sezione Creare nuovi file e cartelle è stato tratto direttamente dall'estensione.

Dopo aver installato l'estensione, apri Chrome DevTools, seleziona la scheda OPFS Explorer e potrai ispezionare la gerarchia dei file. Salva i file dal file system privato di origine al file system visibile all'utente facendo clic sul nome del file ed elimina file e cartelle facendo clic sull'icona del cestino.
Demo
Guarda il file system privato di origine in azione (se installi l'estensione OPFS Explorer ) in una demo che lo utilizza come backend per un database SQLite compilato in WebAssembly. Assicurati di controllare il codice sorgente su GitHub. Tieni presente che la versione incorporata non utilizza il backend del file system privato di origine (perché l'iframe è multiorigine), ma quando apri la demo in una scheda separata, lo fa.
Conclusione
Il file system privato di origine, come specificato dal WHATWG, ha plasmato il modo in cui utilizziamo e interagiamo con i file sul web. Ha consentito nuovi casi d'uso che erano impossibili da ottenere con il file system visibile all'utente. Tutti i principali fornitori di browser, Apple, Mozilla e Google, sono a bordo e condividono una visione comune. Lo sviluppo del file system privato di origine è uno sforzo di collaborazione e il feedback di sviluppatori e utenti è essenziale per il suo progresso.
Mentre continuiamo a perfezionare e migliorare lo standard, sono graditi i feedback sul repository whatwg/fs sotto forma di problemi o richieste di pull.
Link correlati
- Specifica dello standard del file system
- Repository dello standard del file system
- Post di WebKit sull'API File System con il file system privato di origine
- Estensione OPFS Explorer
Ringraziamenti
Questo documento è stato esaminato da Austin Sully, Etienne Noël e Rachel Andrew.