I am developing a TypeScript library and want to migrate the build process from webpack to rollup.
In my project, I have a directory of SVG icons, sorted into several sub-directories. My library should export a JavaScript object that contains the SVG code of all these icons.
With webpack, I was using require.context
to recursively require the SVG files (which were configured as asset/source
):
const context = require.context("../../assets/icons");
export const icons: Record<string, Record<string, string>> = {};
for (const key of context.keys() as string[]) {
const [set, fname] = key.split("/").slice(-2);
if (!icons[set])
icons[set] = {};
icons[set][fname.replace(/\.svg$/, "")] = context(key);
}
How can I achieve this with rollup?
I have had several ideas so far:
require.context
into rollup. However, it seems to be buggy and unmaintained, and in my tests it did not transform the require.context
call at all.rollup-plugin-glob-import
Using rollup-plugin-glob-import in combination with rollup-plugin-string works when configured in the right way.
rollup-plugin-string
generates modules with a string as its default export, so format: 'default'
has to be used for rollup-plugin-glob-import
to import them properly. If sub directory paths should be preserved in the named exports of the resulting bundle, a custom rename
function can be used (note that the export name needs to be a valid JavaScript variable name, hence the use of camelCase
here):
import { defineConfig } from 'rollup';
import globImport, { camelCase } from 'rollup-plugin-glob-import';
import { string } from 'rollup-plugin-string';
export default defineConfig({
...
plugins: [
string({
include: "assets/icons/**/*.svg"
}) as any,
globImport({
format: 'default',
rename: (name, id) => name || `${camelCase(basename(dirname(id)))}_${camelCase(basename(id, extname(id)))}`
})
]
});
rollup-plugin-glob-import
generates named exports but no default export, so the exported files can be imported like this:
import * as icons from '../../assets/icons/**/*.svg';
Writing a custom Rollup plugin is not difficult and allows for more flexibility. In particular, it can be used to export a single JSON object rather than one named export per file (whose name is restricted by the allowed characters for JavaScript variable names).
In this example I'm providing a module called custom:icons
, which has a default export containing all the files:
import { defineConfig } from "rollup";
import glob from 'fast-glob';
import { readFile } from 'fs/promises';
export default defineConfig({
...
plugins: [
{
name: 'custom:icons',
resolveId: (id) => {
if (id === 'custom:icons') {
return id;
}
},
load: async (id) => {
if (id === 'custom:icons') {
const icons: Record<string, Record<string, string>> = {};
for (const path of await glob('./assets/icons/*/*.svg')) {
const [set, fname] = path.split("/").slice(-2);
if (!icons[set])
icons[set] = {};
icons[set][fname.replace(/\.svg$/, "")] = (await readFile(path)).toString();
}
return `export default ${JSON.stringify(icons, undefined, '\t')}`;
}
}
}
]
});
The custom module can then be imported like this:
import icons from 'custom:icons';