DEV Community

aboutandre
aboutandre

Posted on

How to access build time assets during run time with Webpack

TL;DR;
How to retrieve information that is only available during build time when you are running your app.

I want to share a solution to a problem I had last week. I have hundreds of icons that can be used inside a component and these icons should be available inside Storybook.

This is a perfect task to automate.

Everyone that has worked with Webpack, knows that you have global variables that can be used in runtime. Usually isDevelopment or isProduction will be used to check which environment the app is running and deliver the proper assets.

My solution was to retrieve all the icons' names during build time and save them inside a global scoped variable that would then be accessible during run time. For this example I used Vue, but this is something that Webpack would offer in any environment.

// config/storybook/config.js

export const STORYBOOK_ICONS = require
    .context('../../src/assets/icons', true, /^.*\.svg$/) // Comment 1
    .keys()                                               // Comment 2
    .toString()                                           // Comment 3
    .match(/\b(?!svg)\b[^,./].+?(?=\.)/g)
Enter fullscreen mode Exit fullscreen mode

Comment 1
We use here the context API from Webpack. This is a function that receives four parameters (I only used the first three):

require.context(directory, useSubdirectories = true, regExp = /^\.\/.*$/, mode = 'sync');
Enter fullscreen mode Exit fullscreen mode

Comment 2
Then we access all the keys with .keys():

keys is a function that returns an array of all possible requests that the context module can handle.

At the moment this is looking like this:

STORYBOOK_ICONS = [
  "./about.svg",
  "./accept_database.svg",
  "./add_column.svg",
  "./add_database.svg",
  "./add_image.svg",
  "./add_row.svg",
  "./address_book.svg",
  "./advance.svg",
  "./advertising.svg",
  "./alarm_clock.svg",
  ...
]
Enter fullscreen mode Exit fullscreen mode

Thats no good, we want to use the variables inside our component. We need to filter the name out.

Regex to the rescue!

Comment 3
I preferred here to use the .toString() method instead of .filter since I would need to create a new Regex variable and use that to compare with each passed value (as seen in this SO answer). With a string we can then use the .match function that will return an array for every item that it finds with a certain regex.

Talk about killing two birds with one stone!

This creates an array without the file format.

STORYBOOK_ICONS = [
    "about",
    "accept_database",
    "add_column",
    "add_database",
    "add_image",
    "add_row",
    "address_book",
    "advance",
    "advertising",
    "alarm_clock",
    ...
]
Enter fullscreen mode Exit fullscreen mode

Then in the Story that I wanted to have the icons available. I import the variable from the global scope and using reduce and ES6 Dynamic Property Keys to create a shiny new object with key and value pairs that are identical.
Like this:

// src/components/IconSelector/IconSelector.stories.js

import {STORYBOOK_ICONS} from '../../../config/storybook/config';

const iconList = STORYBOOK_ICONS.reduce(function (result, item) {
    result[item] = item;
    return result;
}, {});

Enter fullscreen mode Exit fullscreen mode

Our new object looks like this:

iconList = {
    "about": "about",
    "accept_database": "accept_database",
    "add_column": "add_column",
    "add_database": "add_database",
    "add_image": "add_image",
    "add_row": "add_row",
    "address_book": "address_book",
    "advance": "advance",
    "advertising": "advertising",
    "alarm_clock": "alarm_clock",
    ...
}
Enter fullscreen mode Exit fullscreen mode

Which is then used inside the story:

// src/components/IconSelector/IconSelector.stories.js

storiesOf("Icon Selector", module)
  .addDecorator(withKnobs)
  .add("default", () => {
    return {
      components: { IconSelector },
      props: {
        selectedIcon: {
          default: select(
            'Icons', iconList, Object.keys(iconList)[0]), // <- here
        },
      },
      template: `<IconSelector :icon-name='selectedIcon'/>`
    };
  });

Enter fullscreen mode Exit fullscreen mode

And the final results:

Alt Text

Repo

I published a demo repo that can be found here.

PS

This is my first post ever. If you have any comments or pointers I would be glad to receive feedback! =)

Top comments (0)