Skip to content

Conversation

@davepagurek
Copy link
Contributor

@davepagurek davepagurek commented Dec 27, 2025

Resolves #8364

Changes

So this stems from the fact that p5.strands code gets transpiled, and when we run the result, it's not in the same runtime context as before. It has no access to outside variables. So we had previously made a way to inject those variables: pass them in as an object in a second parameter after your function, and we'll automatically feed them in as parameters to your transpiled function with the original names so that you have access again:

const myp5 = something;
baseFilterShader.modify(() => {
  myp5.someCall(...)
}, { myp5 })

// Turns into this after transpilation:

(myp5) => {
  myp5.someCall(...)
}

The problem was that minified js and ts code also change the variable names, but not the keys of that object you pass in. Previously it worked because those were always the same, but minification makes that no longer be the case. The above code could turn into something like this:

const e = something;
baseFilterShader.modify(() => {
  e.someCall(...)
}, { myp5: e })

// Turns into this after transpilation:

(myp5) => {
  e.someCall(...) // e is not defined!
}

To address that, there are two possible routes:

  • Update the function so it doesn't get minified
  • Update the context object so that the minifier uses the right name

The first option is easier (don't write a function, write a source code string), and that would work for our library, but would make js/ts builds a lot more cumbersome. No one really likes writing code in a string. So I moved on to the second option.

There's already a solution along the lines of the second option in the wild. In Puppeteer, when you write a function in nodejs to run in a Chrome window, it's also recreating a new function. The way it handles it is that it doesn't automatically inject variables for you, you have to manually grab them from function arguments. So that's what I've opted for here:

// Original code:

const myp5 = something;
baseFilterShader.modify(({ myp5 }) => {
  myp5.someCall(...)
}, { myp5 })

// After minification:

const e = something;
baseFilterShader.modify(({ myp5: f }) => {
  f.someCall(...)
}, { myp5: e })

// Turns into this after transpilation:

({ myp5: f }) => {
  f.someCall(...) // all good now!

This is more verbose than before, but I think it's the only way we can avoid this issue.

I also noticed that sometimes the minifier turns for loops like this:

for (let i = 0; i < 5; i++) {
  a += i;
  b += i;
}

...into something like this for compactness, with comma separated statements:

for (let i = 0; i < 5; i++) {
  a += i, b += i;
}

This wasn't getting handled by our transpiler because we were trying to whitelist the statements we need to traverse into to find variable updates in a loop. I've updated that code to now use the default Acord tree traversal, and manually skip the ones we don't want to traverse into.

PR Checklist

@davepagurek davepagurek merged commit a213e53 into dev-2.0 Dec 27, 2025
5 checks passed
@davepagurek davepagurek deleted the fix/minified-strands branch December 27, 2025 20:56
@Aayushdev18
Copy link

Thanks for the detailed explanation! I read through the PR and learned a lot about how minification interacts with transpilation. The destructuring approach is really clever, I wouldn't have thought of that. Appreciate you taking the time to document the process.

@davepagurek davepagurek mentioned this pull request Dec 29, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants