p5.js
p5.js copied to clipboard
Minimising p5.min.js further for production
Topic
So as of today, p5.js is on version 1.4.2 and the minified file stands at a whopping 804KB filesize.
I am a website developer and have been learning and getting better at it since some months now and want to use it on client sites as animated backgrounds to add some zing. I do know that the raw p5.js is a collection of a lot of sub-libraries and with a total size of around 4MBs. How can I shave off unwanted libraries safely from the parent p5.js file and then minimise it for production use?
Someone pointed out to me on FB that the opentype library is the biggest 'file'. But I am not too good with Github I guess as I wasnt able to find this file in the p5.js repo.
I almost never use sound, video, ASCII, typography functions. And if my sketch is purely 2D, the 3D library too doesn't serve me any purpose. So I want to save small sized versions of the p5.min.js file on my system to use on a per project basis. Please help me understand this. I do not want to waste these past months of hard work.
Thank you.
Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, please make sure to fill out the inputs in the issue forms. Thank you!
There were some old work around a modularly built library which may not work anymore. We'll need more interest and developer time to develop that more.
@monolithMktg, it's possible you might find q5.js to suit your needs. It is a "a small and fast alternative (experimental) implementation of p5.js" which is 33kb minified. Note, however, that q5.js is sporadically maintained by a single person, and is only intended to be "mostly code-compatible with p5.js"; support requests may go unanswered.
I took another look today at combineModules
(see #3956) functionality for custom builds as my last custom build was with v0.10.2. At the time, combineModules:min:core/shape:color:math:image uglify
resulted in a 207K build.
Now the same module selection results in a 692K build and my drawing throws errors in the console. It now needs combineModules:min:core/shape:accessibility:color:math:image uglify
which is a 715K build.
The custom p5.js build combineModules
functionality does still work though.
In 2019, it seemed to get the build size down any more we need greater specificity with browserify to remove unused code in each module. There was also some discussion of major changes to the entire build system for better tree shaking but it seemed like either option required a lot more time.
Has there been any further progress on this? I have a site where the before and after of adding P5 is 155KB vs 1.1MB. If the code was written in a more tree shakeable fashion then needing custom builds or "combine modules" would be totally unnecessary. I think refactoring the code to use ES modules with exported functions and classes and no more archaic prototype
usage is likely to be the way forward for sane dist sizes.
For example, let's consider curveTangent
. I'm not using it in this project and yet it's there in my dist anyway.
This is because P5 has been written in a borderline impossible to treeshake fashion. If P5 can move away from prototype
and default exports, this would not happen.
This is how it is today:
p5.prototype.curveTangent = function(a, b, c, d, t) {
p5._validateParameters('curveTangent', arguments);
const t2 = t * t,
f1 = -3 * t2 / 2 + 2 * t - 0.5,
f2 = 9 * t2 / 2 - 5 * t,
f3 = -9 * t2 / 2 + 4 * t + 0.5,
f4 = 3 * t2 / 2 - t;
return a * f1 + b * f2 + c * f3 + d * f4;
};
export default p5;
However it should be written as the following:
import { validateParameters } from './wherever';
export const curveTangent = (a, b, c, d, t) => {
validateParameters('curveTangent', arguments);
const t2 = t * t,
f1 = -3 * t2 / 2 + 2 * t - 0.5,
f2 = 9 * t2 / 2 - 5 * t,
f3 = -9 * t2 / 2 + 4 * t + 0.5,
f4 = 3 * t2 / 2 - t;
return a * f1 + b * f2 + c * f3 + d * f4;
};
Now to be clear, I know some people like the "dump everything into the window object" approach whereby everything is attached to the P5 prototype and then attached to window
. This works OK for the web editor and the docs examples, but in a real world application where instance mode should always be used anyway then for that use case proper tree shakeable code is not just a nice to have but an actual necessity (or else 1.1MB bundles).
This includes introducing real classes instead of, again, attaching functions to the prototype:
p5.Vector = function Vector()
Becomes:
export class Vector {}
This implies essentially two builds. One for the web editor and docs, one for real world production. I think this would be a much more sensible approach going forward instead of trying to build N number of custom builds.
Thoughts?
(Might as well refactor to TS in the process as well)
Here is some more reading on the topic:
https://bluepnume.medium.com/javascript-tree-shaking-like-a-pro-7bf96e139eb7
Make your exports granular and atomic Webpack will generally leave exports fully intact. So if you’re:
Exporting an object with many properties and methods Exporting a class with many methods Using export default and including many things at once Those exports will always be either fully included in the bundle, or fully tree-shaken. That means you may end up including a lot of code which is never used in the final bundle.
Unfortunately I think it's a little more complicated than just refactoring into ES modules and classes.
I think it's just functions that don't use any p5 instance state that can be tree-shaken away. curveTangent
uses no instance state, so it's completely possible to be tree-shaken away if it were an export function
in a module rather than if it were a method on the p5 class. As far as I'm aware, tools like Webpack don't try to tree-shake away class methods (let me know if I'm wrong and some bundlers do, because that ability makes this all much more feasible), so if e.g. you never use the filter
method, since it's a method on an instance, it will still be present.
Whole classes have the potential to be tree-shaken away (e.g. p5.Image), but since p5 instance methods call them (e.g. p5.loadImage
) and those instance methods can't be tree-shaken away, those classes also won't be tree-shaken away unless we do more refactoring. If we don't want to change our APIs, perhaps we'd have to not include those methods in the "core" build, and from separate module added in "full" builds, extend the class to add those methods?
That said, I think refactoring into ES modules and classes still steps us closer to being able to split up the build, and I suspect the majority of our code is able to be refactored in that way. The small exceptions would be a few cases where we share method implementations between classes by saying p5.Class1.prototype.something = p5.Class2.prototype.something
, but those can maybe import and call a common function.
(Might as well refactor to TS in the process as well)
As much as I like TS (I use p5 TS bindings where I work, actually!), I think this is probably a separate discussion, since it would also potentially make new code contributions harder. In any case, ES modules and classes would also make this jump easier, if we decide it's something we ever want to do.
Lots of excellent points, thank you. You are right, it will be a challenge, but something needs to be done to start the process. Additionally I also found this warning from Chrome - seems many polyfills are being shipped. These polyfills are for features that all current browsers implement. If these are removed a (small) saving could be made. If a user is targeting an old browser they could add polyfills manually.
This includes introducing real classes instead of, again, attaching functions to the prototype:
p5.Vector = function Vector()
Becomes:
export class Vector {}
This implies essentially two builds. One for the web editor and docs, one for real world production. I think this would be a much more sensible approach going forward instead of trying to build N number of custom builds.
partly fixed in this PR https://github.com/processing/p5.js/pull/6075. Since all use export
is a too large step.