Ritorno
Tech 4 min di lettura - 7 ago. 22 - Mayeul Le Monies de Sagazan

Ridurre le lentezze di TypeScript di i18n

Questo articolo segue un altro, si consiglia di leggerlo prima: perché-il-mio-progetto-typescript-è-lento.
Nell'ultimo articolo, abbiamo visto come trovare le parti del codice che impiegano più tempo durante la compilazione TypeScript.
In l'esempio dato, avevamo notato che TypeScript impiegava più di 3 secondi per typechecker una parte di codice che utilizzava la libreria di internazionalizzazione i18next, (il che è piuttosto fastidioso quando si devono aspettare 3 secondi prima di avere l'autocompletamento delle chiavi di traduzione).
Risolveremo questo problema in 2 passaggi:
  • Creare uno script per generare l'unione di tutte le chiavi di traduzione
  • Creare un wrapper attorno all'API di i18next per utilizzare la nostra unione per il tipo delle chiavi di traduzione

Perché generare questa unione, i18next può farlo da solo, no?

Sì, ma genera questa unione fondendo le unioni di tutti i workspace i18n. Qui, ne abbiamo 8 con 150 chiavi in ogni workspace. Ora, è la fusione delle unioni dei diversi workspace che sembra impiegare più tempo:
"[...] For instance, to eliminate redundant members from a union, the elements have to be compared pairwise, which is quadratic. This sort of check might occur when intersecting large unions, where intersecting over each union member can result in enormous types that then need to be reduced." (TypeScript performance wiki)
È per questo che voglio costruire questa unione con uno script piuttosto che fondere più unioni con TypeScript.
Puoi accedere al codice di questo esempio qui.

Lo script:

const { writeFileSync } = require("fs");

const anotherTest = require("./fr/anotherTest.json");
const hehe = require("./fr/hehe.json");
const login = require("./fr/login.json");
const menu = require("./fr/menu.json");
const mobile = require("./fr/mobile.json");
const sample = require("./fr/sample.json");
const userErrors = require("./fr/userErrors.json");
const common = require("./fr/common.json");

const keys = [
  ...Object.keys(anotherTest).map((key) => `anotherTest:${key}`),
  ...Object.keys(hehe).map((key) => `hehe:${key}`),
  ...Object.keys(login).map((key) => `login:${key}`),
  ...Object.keys(menu).map((key) => `menu:${key}`),
  ...Object.keys(mobile).map((key) => `mobile:${key}`),
  ...Object.keys(sample).map((key) => `sample:${key}`),
  ...Object.keys(userErrors).map((key) => `userErrors:${key}`),
  ...Object.keys(common).map((key) => `common:${key}`),
];

const keysText = keys.map((key) => `| '${key}'`).join("\n");
const fileText = `export type AllKeysUnion = \n${keysText};\n`;
const FILE_NAME = "AllKeysUnion.ts";

writeFileSync(FILE_NAME, fileText, "utf8");
Questo script è in 3 passaggi:
  • importiamo i file JSON che contengono le chiavi che ci interessano
  • formattiamo le informazioni che ci interessano in una grande stringa di caratteri (la variabile fileText)
  • creiamo il file con fs.writeFile
Questo ci fornisce un file che esporta AllKeysUnion, che è un'unione in cui ogni stringa ha il formato "workspace:chiave"
export type AllKeysUnion =
  | "anotherTest:anotherTest-0"
  | "anotherTest:anotherTest-1"
  | "anotherTest:anotherTest-2"
...

Il wrapper:

import { i18n as I18nInterface, TOptions } from "i18next";
import { useTranslation as useTranslationOriginal } from "react-i18next";
import { AllKeysUnion } from "./AllKeysUnion";

export type TFunctionWrapper = (
  key: AllKeysUnion,
  options?: TOptions
) => string;

export type UseTranslationResponse = {
  t: TFunctionWrapper;
  i18n: I18nInterface;
  ready: boolean;
};

export function useTranslationWrapper(): UseTranslationResponse {
  const { t, i18n, ready } = useTranslationOriginal();

  const myT = (key: AllKeysUnion, options?: TOptions) => t(key, options);

  return { t: myT, i18n, ready };
}
In questo file, il più importante è il nostro wrapper useTranslationWrapper, è questa funzione che utilizzeremo al posto del useTranslation di i18next nel nostro codice React.
Questa funzione utilizzerà l' useTranslation originale di i18next ma essendo tipizzata con l'unione TypeScript che abbiamo generato grazie allo script realizzato in precedenza.

Utilizzare il wrapper:

Non ci resta che utilizzare questo wrapper nel nostro codice React:
Per questo, basta sostituire le chiamate a useTranslation tramite chiamate a useTranslationWrapper.
In App.tsx :
const { t } = useTranslationWrapper();
È tutto ciò che dobbiamo cambiare.

Verificare che i rallentamenti siano spariti :

Per essere certi che tutto questo lavoro non sia stato vano, useremo gli stessi strumenti che in l'articolo precedente per generare i file di report della compilazione prima e dopo le modifiche apportate.
Una volta generati i report di compilazione, non ci resta che usare lo strumento @typescript/analyze-trace per contemplare il risultato.
Prima :
Hot Spots
└─ Check file /home/mayeul/Projects/tests/test-tsc/src/App.tsx (3700ms)
   └─ Check deferred node from (line 15, char 9) to (line 16, char 33) (3186ms)
      └─ Check expression from (line 16, char 10) to (line 16, char 20) (3172ms)
         └─ Check expression from (line 16, char 21) to (line 16, char 29) (3170ms)
            └─ Check expression from (line 16, char 22) to (line 16, char 28) (3170ms)
Dopo :
Found nothing in 1 project(s)
Lo strumento non rileva più codice particolarmente lento da compilare, e il progetto viene compilato in meno di 1 secondo, contro più di 3 secondi prima !
Nota : Per questo articolo, abbiamo realizzato un wrapper molto minimalista, non abbiamo ad esempio permesso di ottenere una funzione di traduzione relativa a un solo workspace, cosa che è possibile con i18next : useTranslation(workspace), detto questo, nulla ci impedisce di migliorare il wrapper.

Desideri essere accompagnato per lanciare il tuo progetto digitale ?

Invia il tuo progetto ora