Skip to content

Importing XML as text

Recently, I wanted to have a way to import XML files as a string in my project.

So, for example, if I had a file rss.xml:

rss.xml
<rss version="2.0">
<channel>
<title>Hacker News</title>
<link>https://news.ycombinator.com/</link>
<description>Links for the intellectually curious, ranked by readers.</description>
<item>
<title>Extreme Pi Boot Optimization</title>
<link>https://kittenlabs.de/blog/2024/09/01/extreme-pi-boot-optimization/</link>
<pubDate>Sun, 1 Sep 2024 21:36:55 +0000</pubDate>
<comments>https://news.ycombinator.com/item?id=41420597</comments>
<description>
<![CDATA[<a href="https://news.ycombinator.com/item?id=41420597">Comments</a>]]>
</description>
</item>
<item>
<title>Programming Zero Knowledge Proofs: From Zero to Hero</title>
<link>https://zkintro.com/articles/programming-zkps-from-zero-to-hero</link>
<pubDate>Fri, 30 Aug 2024 06:08:41 +0000</pubDate>
<comments>https://news.ycombinator.com/item?id=41398092</comments>
<description>
<![CDATA[<a href="https://news.ycombinator.com/item?id=41398092">Comments</a>]]>
</description>
</item>
</channel>
</rss>

I want to be able to import the full content of the document as a string in my project:

src/index.ts
import rss from './rss.xml';
console.log(rss);

TypeScript#

We need to let TypeScript know how to handle XML files to resolve the following error:

Terminal window
Cannot find module './resources/rss.xml' or its corresponding type declarations.ts(2307)

We can do this by adding a declaration file xml.d.ts:

xml.d.ts
declare module '*.xml' {
const text: string;
export default text;
}
  • Directorysrc
    • Directoryresources
      • rss.xml
    • xml.d.ts
    • index.ts
  • tsconfig.json

Bundling with ESBuild#

But types don’t exist at runtime. So, how do we actually import the XML file? Basically, we want to read the content of the file and then export it as a string.

import { readFile } from 'node:fs/promises';
const rss = await readFile('./resources/rss.xml', 'utf-8');
export default rss;

We can tell esbuild to do this by using the text loader:

runEsbuild.ts
import { build } from "esbuild";
import esbuildPluginPino from "esbuild-plugin-pino";
build({
entryPoints: ["./src/startServer.ts"],
bundle: true,
platform: "node",
outdir: "./dist",
minify: true,
loader: {
".xml": "text",
},
plugins: [esbuildPluginPino({ transports: ["pino-pretty"] })],
}).catch((error) => console.error(error));

If this doesn’t fit your needs, you can create your own plugin like esbuild-plugin-inline-import.

Vite / Vitest#

vitest uses vite under the hood for running your tests. With vite, you can import XML files directly using ?raw:

import rss from './resources/rss.xml?raw';

Unfortunately, since I was using esbuild for bundling, I couldn’t modify the import path to include ?raw. So, instead I created a plugin to tell vite how to handle the XML files:

vitest.config.ts
import { readFile } from 'node:fs/promises';
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
include: ['**/src/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
},
plugins: [
{
name: 'handle-xml',
async transform(_, id) {
const xmlRegex = /\.xml$/;
if (!xmlRegex.test(id)) {
return;
}
const xml = (await readFile(id)).toString();
return {
code: `export default \`${xml}\``,
};
},
},
],
});

Essentially, we are filtering for XML files and then reading the content of the file and exporting it as a string. This took inspiration from vite-plugin-xml-loader.

Resources#