designsystemsinternational / mechanic

Mechanic is a framework to build assets built on web code.
https://mechanic.design
MIT License
253 stars 11 forks source link

[Discussion] Mechanic built-in preload utility function #149

Closed lucasdinonolte closed 1 year ago

lucasdinonolte commented 2 years ago

Context

Some functions need to run slow code (like loading a bigger static file) upfront. This code is currently re-run on every preview, which can make things feel slow and less snappy in the UI.

To solve this the concept of function memoization could be used. Essentially a memoized function creates a cache based on it's input and immediately returns the cache, if its inputs didn't change – for example the useMemo hook in React.

This PR explores the possibilities of adding memoization as a utility to Mechanic. It should serve as a starting point for debate how we want to implement this.

The actual implementation of memoization is fairly simple. The hard part is really: How do we want to expose this function to the users?

Prototypical Solution

const font = await mechanicPreload(async () => await loadOpentypeFont(fontFile), [fontFile]);

Other approaches to memoization

"Default" memoization in functional programming

The above implementation was chosen for the prototype based on the assumption that if people know about memoization they probably know about the way React does it

Traditionally memoization is realized by returning a memoizable function from another function (this sounds more confusing than the code looks):

const fontLoader = makeMemoizableFunction(async (fontFile) => await loadOpentypeFont(fontFile));
const font = await fontLoader(inputs.font);

While personally I find this approach more explicit I'm unsure if this is the right way to expose this functionality as this is deeply tied to functional programming, which probably not all of mechanic's users are super familiar with.

Ideal solution

The ideal solution from a user's perspective would be to not needing to worry about memoization and dependencies at all. Just wrap the things you want to cache inside a preload function and Mechanic will figure out how to cache it.

const font = await mechanicPreload(async () => await loadOpentypeFont(fontFile));

However this is also the hardest to implement. For the above example I can't think of a way for Mechanic to know about the function's dependencies (which it needs to build the cache key) that wouldn't involve some sort of parsing and AST magic. But maybe I'm missing an obvious solution.

TL;DR – I just want to see this in action

Logo Canvas on DSI Logo Maker has been updated to use memoization on the font loading. You can compare this to another design function to see the difference in rendering this makes.

runemadsen commented 2 years ago

This is really lovely! I only have two small comments:

  1. Are we sure that the mechanicPreload function isn't resettting its memoed objects whenever the design function is run, since the design function is re-run in a completely newly generated iframe?
  2. I'm sold on the function syntax, but I'm wondering about the name. Since it's used in the context of the design function and not before, how about something like just memo? People can always rename in the import if they want.

Once these are taken care of, let's get it in :)

lucasdinonolte commented 2 years ago
  1. The way it's set up it only resets on page reload. JavaScript modules are singletons by default. By importing it directly from mechanic and not passing it into the handler function we make sure there only ever is one instance of the memoization cache around. The iframe sets up the function initially and then provides a run function that executes the handler with the current values again. So it's not refreshing the page and the memoization function inside the handler still points to the singleton.
  2. Agreed, I'll rename the function and rebase this PR to point to main and then request official review :blush:
runemadsen commented 2 years ago

Excelllllleeeeent. Let's get it in! And let's remember to have a list of things to document for the docs.

netlify[bot] commented 2 years ago

Deploy Preview for dsi-logo-maker ready!

Name Link
Latest commit 61542aabde9b04cb90e661d65500c465fb93fa53
Latest deploy log https://app.netlify.com/sites/dsi-logo-maker/deploys/63e3c77f418915000864d435
Deploy Preview https://deploy-preview-149--dsi-logo-maker.netlify.app
Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

lucasdinonolte commented 1 year ago

I updated this, the big merge with the prettified main messed a few things up. So to show the new behavior I updated Logo Canvas on DSI Logo Maker with the new memo util. So you can switch between Logo Canvas and Logo SVG on the deploy preview to see the difference.

For Logo Canvas you shouldn't see white flashes when editing a non-font parameter, while for the un-memoed Logo SVG you should see a white flash for every input change.

@runemadsen @fdoflorenzano would you mind giving this a quick final pass?