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)
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');
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",
...
]
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",
...
]
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;
}, {});
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",
...
}
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'/>`
};
});
And the final results:
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)