frctl / fractal

A tool to help you build and document website component libraries and design systems.
https://fractal.build
MIT License
2.11k stars 169 forks source link

Extendable context references in config files #1055

Open mkoe-unitb opened 3 years ago

mkoe-unitb commented 3 years ago

Normal config files with context references looks like:

module.exports = {
    title: 'teaser-list',
    context: {
        items: [
            '@molecules-teaser',
            '@molecules-teaser',
            '@molecules-teaser',
        ]
    }
}

The Problem I have in multiple projects is, that I couldn't extend the referenced object. This wouldn't work: const extendedTeaserContext = Object.assign('@molecules-teaser', { newVariable: 'lorem ipsum' })

I have two ideas to solve this problem.

Possible Solution 1: getContext Function A function to convert a context reference into the object. const extendedTeaserContext = Object.assign(getContext('@molecules-teaser'), { newVariable: 'lorem ipsum' })

Possible Solution 2: Post Context Generation Hook A function to adjust the context after all references are resolved.

module.exports = {
    title: 'teaser-list',
    context: {
        items: [
            '@molecules-teaser',
            '@molecules-teaser',
            '@molecules-teaser',
        ]
    },
    postReferenceResolvedHook(context) {
        const extendedItems = [];

        for (const item of context.items) {
            item.newVariable = 'lorem ipsum';
            delete item.globalContextVariable;
            extendedItems.push(item)
        }

        context.items = extendedItems;
        return context;
    }
}
mihkeleidast commented 3 years ago

I like the idea of proposed solution 1 more, because it'll open up more possibilities with using different context helpers in the future (for example, adding a render context helper function instead of the current @@handle syntax). The second solution seems too manual, especially if the context object is large (and it may be) and has multiple instances of the objects in different places.

RickMeijer commented 3 years ago

I was just about to file an issue/help regarding this, example repository and all! Guess it's not necessary. For us this feature would be great, since we try to follow the atomic design principles, and try not to copy the properties belonging to the atoms all over the codebase. Of course there's the render helper with merge=true, but this isn't much use when using @partial-blocks.

How about fractal.components.find() returning a promise? You could then use the .context property on the found item, instead of it returning undefined. After that you can handle the context however way you wish.

If that's impossible (like doing the .find after a load(), which is never triggered since the context would never be resolved), maybe add a hook to each loaded component? Something like fractal.components.on("load:componentName") would help greatly.

Something like this below works, but is hackneyed at best, and malicious at worst. You could spend days figuring out why on some machines it compiles correctly, while on others it's not.

function findPromise(componentName, waitForIt=100) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const atom = fractal.components.find(componentName);
      if (!atom) reject(new Error("No atom found"));
      fractal.components.resolve(atom.context).then(resolve);
    }, waitForIt); // Random number depending on computing power
  });
}

module.exports = {
  context: {
    item: findPromise("@atom--list").then(context => ({
      ...context,
      items: [ 'fi', 'fie', 'fo']
  })
}