Skip to main content

Command Palette

Search for a command to run...

Importing TOML in React Native

Updated
2 min read

This is a no bullshit quick note that I also can use in the future.

I love to use TOML in i18next because I also comment the translations to give them context. The problem is, there’s no standard way to simply import TOML files in a React Native project.

Metro is the bundler for React Native projects. In our case, it has a very useful feature: Transforming source codes.

Transforming is basically taking in a source code that is not native to JS (like TOML, YAML etc.), and converting it into native JS type. Of course, this is oversimplified.

So, when you do import enTranslation from “@/assets/translations/en.toml”, Metro will parse TOML and convert it into native JS.

We also need a parser for TOML, and I think smol-toml is the most maintained one at the time I am writing this.

So, let’s first create metro.transformer.js and add this code:

// import transformer
const upstreamTransformer = require("@expo/metro-config/babel-transformer");
// import parser
const { parse } = require("smol-toml");

module.exports = {
  // export custom transform function
  transform: (config) => {
    // spread config, we need individual vars
    const { src, filename, options } = config;

    // if file is toml
    if (filename.endsWith(".toml")) {
      try {
        // parse it
        const parsedData = parse(src);
        // act like it's a JS module and export the data
        const code = `module.exports = ${JSON.stringify(parsedData, null, 2)};`;

        // transform the result
        return upstreamTransformer.transform({
          src: code,
          filename,
          options,
        });
      } catch (error) {
        // if there's error, do not compile
        throw new Error(
          `Error parsing TOML file ${filename}: ${error.message}`
        );
      }
    }

    // Use default transformer for other files
    return upstreamTransformer.transform(config);
  },
};

This is not where it ends, though. We also need to add it to metro.config.js.

const { getDefaultConfig } = require("expo/metro-config");

// get default config
const config = getDefaultConfig(__dirname);

// introduce transformer into default config
config.transformer = {
  ...config.transformer,
  // define transformer path here
  babelTransformerPath: require.resolve("./metro.transformer.js"),
};

// introduce resolver, without this, import will not recognize toml imports
config.resolver = {
  ...config.resolver,
  sourceExts: [...config.resolver.sourceExts, "toml"],
};

module.exports = config;

After this, you can safely import TOML files:

import enTranslations from "@/assets/translations/en.toml";

Of course, you will see some problems about Typescript being unable to define types. Create toml.d.ts at root of your project and define type:

declare module "*.toml" {
  const value: Record<string, unknown>;
  export default value;
}

And add it to your tsconfig.json.

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": [
    "**/*.ts",
    "**/*.tsx",
    ".expo/types/**/*.ts",
    "expo-env.d.ts",
    "nativewind-env.d.ts",
    "toml.d.ts", // add type definition file here
  ]
}