Recent blog posts

Pete Eamsuwan11/23/2022

Load files dynamically with Webpack

Learn how to use Webpack's require.context to dynamically load markdown files for a static blog


While working on this blog, I faced a challenge. One way to add new content to the blog was to manually import each markdown file containing the blog content into a configuration file, which would then be used to render the content in the react components.

However, this would require manual importing for every new blog I wanted to add, which is not ideal. Instead, I wanted a solution where I could simply drop in the markdown, rebuild, deploy, and be done.

This led me to consider using Webpack's require context feature.

What is a Context?


In Webpack, a context is a reference to a directory in the file system. When you create a context using require.context, you specify the directory that the context should be based on, as well as some other optional parameters.

Webpack's require.context


Webpack's

require.context
function allows you to create a context in which you can use the require function to dynamically require a list of modules. This can be useful in cases where you want to require a group of modules in a directory and don't want to manually specify each module.

The

require.context
function takes three arguments:

  • The directory to search for modules
  • A flag indicating whether to search subdirectories
  • A regular expression to match against filenames

For example, in my case I would be looking at something like this:

- assets/
  - posts/
    - blog_post_1.md
    - blog_post_2.md
    - blog_post_3.md

So, instead of manually importing each components like this:

import BlogPost1 from './assets/posts/blog_post_1.md';
import BlogPost2 from './assets/posts/blog_post_2.md';
import BlogPost3 from './assets/posts/blog_post_3.md';

You can use

require.context(...)
to load all modules in the components directory like this:

const markdownContext = require.context('../assets/posts', false, /\.md$/); // line 1
const getModules = (context: __WebpackModuleApi.RequireContext) => context.keys().map(context); // line 2
const markdownModules = getModules(markdownContext); // line 3

Wait! so how does line 2 work?

context.keys()
returns an array of strings, where each string is the relative path to a module in the context. The map function iterates over this array and uses the context function to load each module.

So, if the

context.keys()
array contained the following strings:

['./assets/posts/blog_post_1.md', './assets/posts/blog_post_2.md', './assets/posts/blog_post_3.md'];

Then the

context.keys().map(context)
expression would be equivalent to:

const BlogPost1 = require('./assets/posts/blog_post_1.md');
const BlogPost2 = require('./assets/posts/blog_post_2.md');
const BlogPost3 = require('./assets/posts/blog_post_3.md');

The results from the

getModules
function will be dependent on which Webpack loader you use to load the markdown files, in my case, I am using:

...
{
  test: /\.md$/,
  loader: 'frontmatter-markdown-loader',
  options: {
    mode: [Mode.BODY],
  },
},
...

Which conveniently returns the frontmatter containing all the meta-data for each blog post and the actual content body itself.

If you are interested to see this working in action, please checkout the source code at my github repo.

Resources