Bun : Fichiers et répertoires
Tags: javascript, bunjs, slidesk
Published: 2023-08-29 19:38:49

Dans l'[article précédent](https://dev.to/gouz/slidesk-une-introduction-par-bun-2km2), nous avons découvert _Bun_ et créé un programme qui affiche la version de _Bun_ depuis la commande `bunx monprogramme`.

Dans cet article, nous allons voir comment _Bun_ simplifie les interactions avec les fichiers.

## Lire un fichier

Pour lire un fichier, il faut utiliser la fonction `Bun.file()`.

Cette fonction est synchrone, contrairement à la lecture du contenu qui est asynchrone.

Ainsi, la lecture complète d'un fichier se fait de la manière suivante :

```js
const myFile = Bun.file("./package.json");
const pkg = await myFile.json(); // il existe aussi text(), stream()
console.log(pkg.name); // devrait afficher "monprogramme"
```

La fonction `file()` prend en paramètre une chaîne de caractères qui définit le chemin du fichier à traiter et retourne un objet de type _BunFile_ avec 2 propriétés utiles :

- size : retourne la taille du fichier
- type : retourne le MIME du fichier

```ts
file(path: string | number | URL, options?: { type?: string }): BunFile;
```

Structure de l'objet _BunFile_ :

```ts
interface BunFile {
  readonly size: number;
  readonly type: string;

  text(): Promise<string>;
  stream(): Promise<ReadableStream>;
  arrayBuffer(): Promise<ArrayBuffer>;
  json(): Promise<any>;
  writer(params: { highWaterMark?: number }): FileSink;
}
```

On voit donc qu'il existe en plus des 2 propriétés, 5 fonctions qui permettent de manipuler le contenu :

- `text()` : cette méthode retourne le contenu du fichier sous le format d'une chaîne de caractères ;
- `stream()` : cette méthode retourne le contenu dans un objet _ReadableStream_ ;
- `arrayBuffer()` : cette méthode retourne le contenu dans un _ArrayBuffer_ ;
- `json()` : très utile dans la manipulation de fichier `.json`, cette méthode prend le contenu du fichier et le transforme en _JSON_ pour faciliter la manipulation de celui-ci.

La méthode `writer()` est très utile dans la manipulation de _Chunk_, nous y reviendrons par la suite.

## Écrire dans un fichier

Maintenant que nous avons vu comment lire un fichier, nous allons voir comment écrire dans un fichier existant ou non.

```ts
write(
  destination: string | number | BunFile | URL,
  input:
    | string
    | Blob
    | ArrayBuffer
    | SharedArrayBuffer
    | TypedArray
    | Response,
): Promise<number>;
```

Comme on peut le voir sur la définition de la méthode `write()`, cette méthode retourne une promesse. Elle est donc asynchrone.

Ainsi si nous modifions notre fichier `bin/index.js` de l'article précédent par :

```js
#!/usr/bin/env bun

const data = `Bun version: ${Bun.version}`;
await Bun.write("output.txt", data);
```

En lançant la commande `bun bin/index.js`, nous obtenons un fichier à la racine `output.txt` contenant :

```txt
Bun version: 0.7.3
```

De ce fait, nous avons vu qu'il est très simple d'écrire une chaîne de caractères dans un fichier non existant.

Comme nous pouvons le constater dans la définition de la méthode, il est possible de passer autre chose qu'une chaîne de caractères, il est possible de passer une _Response_ comme un _BunFile_.

Par exemple, il est simple de créer la copie d'un fichier :

```js
#!/usr/bin/env bun

const data = `Bun version: ${Bun.version}`;
await Bun.write("output.txt", data);

const input = Bun.file("output.txt");
const output = Bun.file("copy.txt");
await Bun.write(output, input);
```

En relançant la commande `bun bin/index.js` ou la commande `bunx monprogramme`, nous obtenons maitenant 2 fichiers à la racine `output.txt` et `copy.txt` contenant tous les deux :

```txt
Bun version: 0.7.3
```

## Ecrire un fichier en plusieurs fois pour économiser de la mémoire

Lorsque l'on manipule des fichiers lourds, on peut être parfois limité par la mémoire. _Bun_ propose une solution en utilisant les _FileSink_.

Comme dit dans le chapitre "Lire un fichier", nous devions revenir sur la méthode `writer()`.

C'est le moment.

Modifions notre fichier `bin/index.js` par le contenu suivant :

```js
#!/usr/bin/env bun

const file = Bun.file("output.txt");
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB

writer.write("Première ligne\n");
writer.write("Seconde ligne\n");

writer.flush();

writer.write("Troisième ligne\n");
writer.write("Dernière ligne\n");

writer.end();

writer.unref();
```

Une fois exécuté avec la commande `bun bin/index.js` ou la commande `bunx monprogramme`, nous obtenons le fichier `output.txt` avec le contenu suivant :

```txt
Première ligne
Seconde ligne
Troisième ligne
Dernière ligne
```

Maintenant essayons de comprendre les lignes que nous avons copiées.

```js
const file = Bun.file("output.txt");
```

Nous souhaitons enregistrer notre contenu dans le fichier `output.txt`.

```js
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB
```

Nous créons un "buffer" dans lequel nous allons écrire. Ici nous avons précisé un `highWaterMark` à 1 Mo (1024 Ko, donc 1024 \* 1024 octets). Ainsi une fois que le contenu du "buffer" dépasse cette limite, le contenu est écrit dans le fichier, libérant ainsi de la mémoire.

```js
writer.write("Troisième ligne\n");
```

Cette ligne montre comment ajouter du contenu dans le "buffer".

```js
writer.flush();
```

La méthode `flush()` permet d'écrire le contenu du "buffer" dans le fichier et libérer ainsi de la mémoire.

```js
writer.end();
```

Cette ligne signifie d'écrire le contenu et de finaliser l'écriture.

```js
writer.unref();
```

Cette ligne est optionnelle, mais très utile pour éviter des fuites de mémoire. On libère le référencement du "buffer". La méthode `ref()` permet de le référencer à nouveau.

Voilà, maintenant nous avons vu globalement comment écrire dans un fichier.

## Répertoires : manipulation

_Bun_ ne dispose pas d'API pour les répertoires (du moins pour la version actuelle lors de l'écriture de cet article).

Nous devons donc utiliser la bibliothèque `fs` présente dans Node.js.

Modifions notre célèbre fichier `bin/index.js` dans le dossier `monprogramme` par le code suivant :

```js
#!/usr/bin/env bun
import { readdirSync } from "fs";

const data = `Bun version: ${Bun.version}`;
await Bun.write("output.txt", data);

const filenames = readdirSync(__dirname);

console.log("\n Binary directory filenames:");
filenames.forEach((file) => {
  console.log(file);
});

const localfilenames = readdirSync(".");

console.log("\nCurrent directory filenames:");
localfilenames.forEach((file) => {
  console.log(file);
});
```

Nous obtenons le résultat suivant :

```bash
$ bunx monprogramme

Binary directory filenames:
index.js

Current directory filenames:
output.txt
```

Nous avons donc la possibilité d'accèder aux fichiers présents dans le dossier du binaire qu'on execute avec `__dirname` et également la lecture du dossier d'où le programme s'exécute `"."`.

## Imports

Traditionnellement, les imports se font sur des bibliothèques préalablement installées.

Avec _Bun_, pas forcément besoin d'installer ces bibliothèques :

```js
import * from "commander"
```

En écrivant cette ligne, lors de l'exécution _Bun_ va voir s'il connait cette bibliothèque, s'il ne l'a jamais rencontrée, il va la télécharger dans son cache.

Il est également possible de spécifier des versions avec la notation _npm_.

```js
import * from "commander@11.0.0"
```

Bon, à part une gestion équivalente à la _pnpm_, rien de nouveau.

Eh bien si, les imports de fichiers de type `json` ou `toml` sont automatiquement convertis en Objet JavaScript.

Modifions notre fichier `package.json` de `monprogramme` :

```json
{
  "name": "monprogramme",
  "version": "0.0.1",
  "bin": {
    "monprogramme": "./bin/index.js"
  }
}
```

Et modifions notre fichier `bin/index.js` par :

```js
#!/usr/bin/env bun

import pkg from "../package.json";

const data = `version: ${pkg.version}`;
await Bun.write("output.txt", data);
```

Après une exécution de `bunx monprogramme`, on obtient un fichier `output.txt` qui contient :

```txt
version: 0.0.1
```

Un autre avantage de _Bun_ est que si un format de fichier lui est inconnu, il importe le contenu du fichier et le stocke dans une chaîne de caractères.

Imaginons un fichier `test.txt` avec le contenu suivant :

```txt
Ceci est un test
```

Si nous faisons un import comme l'exemple suivant :

```js
import test from "./test.txt";

console.log(test);
```

L'exécution du fichier devrait afficher `Ceci est un test`.

## Comment _SliDesk_ utilise les fichiers ?

_SliDesk_ n'écrit pas de fichier, sauf si on utilise l'option `--save` pour générer des fichiers `.html` par langue pour les héberger sur Internet.

La lecture de fichier est utilisée pour les fichiers de traductions, et également pour les "assets" (images, fonts, ...) qui doivent être fournis pour la présentation.

L'avantage de _Bun_ sur l'import des fichiers en tant que chaîne de caractères a été très utile. En effet, une volonté de simplification des "layouts" (modèles de fichiers pour le thème, les slides, l'interaction, ...) était voulue pour être des fichiers à part, facilement modifiables.

Dans le fichier `core/Interpreter.js` :

```js
import layoutHTML from "../templates/layout.html.txt";
import themeCSS from "../templates/theme.css.txt";
import mainJS from "../templates/main.js.txt";
import gamepadJS from "../templates/gamepad.js.txt";
import qrcodeLibJS from "../templates/qrcode.lib.js.txt";
```

par exemple.

Dans le prochain article, nous verrons comment faire un serveur Web et gérer des WebSockets.