From d31f77c2316653db517ac7d363ee541b957e96a9 Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Tue, 23 Dec 2025 12:42:32 +0000 Subject: [PATCH 1/7] Update dependency and package gifenc --- package-lock.json | 8 ++++---- package.json | 2 +- rollup.config.mjs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6be97b491a..7fb7e54354 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.2.0-rc.3", "license": "LGPL-2.1", "dependencies": { - "@davepagurek/bezier-path": "^0.0.2", + "@davepagurek/bezier-path": "^0.0.7", "@japont/unicode-range": "^1.0.0", "acorn": "^8.12.1", "acorn-walk": "^8.3.4", @@ -355,9 +355,9 @@ } }, "node_modules/@davepagurek/bezier-path": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@davepagurek/bezier-path/-/bezier-path-0.0.2.tgz", - "integrity": "sha512-4L9ddgzZc9DRGyl1RrS3z5nwnVJoyjsAelVG4X1jh4tVxryEHr4H9QavhxW/my6Rn3669Qz6mhv8gd5O/WeFTA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@davepagurek/bezier-path/-/bezier-path-0.0.7.tgz", + "integrity": "sha512-CVlnCOrV1iy4Z12T756i9l4G6kF7r8uhlnb+xqDemAMmWQB+8Q0b+8VEqIiUfywgZDSiDr18Rm7pZlnA69rE8Q==", "license": "MIT" }, "node_modules/@es-joy/jsdoccomment": { diff --git a/package.json b/package.json index a631f6ba88..21d6b92aad 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "version": "2.2.0-rc.3", "dependencies": { - "@davepagurek/bezier-path": "^0.0.2", + "@davepagurek/bezier-path": "^0.0.7", "@japont/unicode-range": "^1.0.0", "acorn": "^8.12.1", "acorn-walk": "^8.3.4", diff --git a/rollup.config.mjs b/rollup.config.mjs index ce03d12124..208499561c 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -193,7 +193,7 @@ export default [ format: 'es', dir: 'dist' }, - external: /node_modules/, + external: /node_modules\/(?!gifenc)/, plugins }, ...generateModuleBuild() From f645136834506b3584e7e9b41800aef5e44a62b5 Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Wed, 24 Dec 2025 16:08:29 +0000 Subject: [PATCH 2/7] Move some references from window to globalThis --- package.json | 1 + src/core/environment.js | 5 ++--- src/core/main.js | 10 +++++----- src/shape/2d_primitives.js | 1 - 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 21d6b92aad..0815655f85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "p5", "repository": "processing/p5.js", + "type": "module", "scripts": { "build": "rollup -c", "dev": "vite preview/", diff --git a/src/core/environment.js b/src/core/environment.js index 88867ef78f..480d1de1fc 100644 --- a/src/core/environment.js +++ b/src/core/environment.js @@ -13,7 +13,7 @@ function environment(p5, fn, lifecycles){ const standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT]; fn._frameRate = 0; - fn._lastFrameTime = window.performance.now(); + fn._lastFrameTime = globalThis.performance.now(); fn._targetFrameRate = 60; const _windowPrint = window.print; @@ -868,7 +868,6 @@ function environment(p5, fn, lifecycles){ * */ fn.fullscreen = function(val) { - // p5._validateParameters('fullscreen', arguments); // no arguments, return fullscreen or not if (typeof val === 'undefined') { return ( @@ -946,7 +945,6 @@ function environment(p5, fn, lifecycles){ * @returns {Number} current pixel density of the sketch. */ fn.pixelDensity = function(val) { - // p5._validateParameters('pixelDensity', arguments); let returnValue; if (typeof val === 'number') { if (val !== this._renderer._pixelDensity) { @@ -1271,6 +1269,7 @@ function environment(p5, fn, lifecycles){ const screenPosition = matrix.multiplyAndNormalizePoint(worldPosition); return screenPosition; }; + /** * Converts 2D screen coordinates to 3D world coordinates. * diff --git a/src/core/main.js b/src/core/main.js index 8e2d292f1d..c522fbf90a 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -245,7 +245,7 @@ class p5 { // Record the time when setup starts. millis() will start at 0 within // setup, but this isn't documented, locked-in behavior yet. - this._millisStart = window.performance.now(); + this._millisStart = globalThis.performance.now(); const context = this._isGlobal ? window : this; if (typeof context.setup === 'function') { @@ -266,8 +266,8 @@ class p5 { } } - this._lastTargetFrameTime = window.performance.now(); - this._lastRealFrameTime = window.performance.now(); + this._lastTargetFrameTime = globalThis.performance.now(); + this._lastRealFrameTime = globalThis.performance.now(); this._setupDone = true; if (this._accessibleOutputs.grid || this._accessibleOutputs.text) { this._updateAccsOutput(); @@ -278,7 +278,7 @@ class p5 { // Record the time when the draw loop starts so that millis() starts at 0 // when the draw loop begins. - this._millisStart = window.performance.now(); + this._millisStart = globalThis.performance.now(); } // While '#_draw' here is async, it is not awaited as 'requestAnimationFrame' @@ -288,7 +288,7 @@ class p5 { // and 'postdraw'. async _draw(requestAnimationFrameTimestamp) { if (this.hitCriticalError) return; - const now = requestAnimationFrameTimestamp || window.performance.now(); + const now = requestAnimationFrameTimestamp || globalThis.performance.now(); const timeSinceLastFrame = now - this._lastTargetFrameTime; const targetTimeBetweenFrames = 1000 / this._targetFrameRate; diff --git a/src/shape/2d_primitives.js b/src/shape/2d_primitives.js index 96a5d530e8..8569082bb9 100644 --- a/src/shape/2d_primitives.js +++ b/src/shape/2d_primitives.js @@ -1214,7 +1214,6 @@ function primitives(p5, fn){ * * */ - /** * @method rect * @param {Number} x From ba3407d2ba36f32bef7a2545f92815421511e1cf Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Wed, 24 Dec 2025 18:02:09 +0000 Subject: [PATCH 3/7] Have core work in node.js Uses in node.js will need additional reimplementation of functionalities otherwise provided by window or document. --- src/core/environment.js | 39 ++++++++++------------- src/core/main.js | 70 +++++++++++++++++++++++------------------ src/core/p5.Renderer.js | 4 ++- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/core/environment.js b/src/core/environment.js index 480d1de1fc..7175319c14 100644 --- a/src/core/environment.js +++ b/src/core/environment.js @@ -11,12 +11,13 @@ import * as C from './constants'; function environment(p5, fn, lifecycles){ const standardCursors = [C.ARROW, C.CROSS, C.HAND, C.MOVE, C.TEXT, C.WAIT]; + const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined'; fn._frameRate = 0; fn._lastFrameTime = globalThis.performance.now(); fn._targetFrameRate = 60; - const _windowPrint = window.print; + const windowPrint = isBrowser ? window.print : null; let windowPrintDisabled = false; lifecycles.presetup = function(){ @@ -24,11 +25,13 @@ function environment(p5, fn, lifecycles){ 'resize' ]; - for(const event of events){ - window.addEventListener(event, this[`_on${event}`].bind(this), { - passive: false, - signal: this._removeSignal - }); + if(isBrowser){ + for(const event of events){ + window.addEventListener(event, this[`_on${event}`].bind(this), { + passive: false, + signal: this._removeSignal + }); + } } }; @@ -64,9 +67,9 @@ function environment(p5, fn, lifecycles){ * */ fn.print = function(...args) { - if (!args.length) { + if (!args.length && windowPrint !== null) { if (!windowPrintDisabled) { - _windowPrint(); + windowPrint(); if ( window.confirm( 'You just tried to print the webpage. Do you want to prevent this from running again?' @@ -218,7 +221,7 @@ function environment(p5, fn, lifecycles){ * * */ - fn.focused = document.hasFocus(); + fn.focused = isBrowser ? document.hasFocus() : true; /** * Changes the cursor's appearance. @@ -628,7 +631,7 @@ function environment(p5, fn, lifecycles){ * @alt * This example does not render anything. */ - fn.displayWidth = screen.width; + fn.displayWidth = isBrowser ? window.screen.width : 0; /** * A `Number` variable that stores the height of the screen display. @@ -659,7 +662,7 @@ function environment(p5, fn, lifecycles){ * @alt * This example does not render anything. */ - fn.displayHeight = screen.height; + fn.displayHeight = isBrowser ? window.screen.height : 0; /** * A `Number` variable that stores the width of the browser's viewport. @@ -794,21 +797,11 @@ function environment(p5, fn, lifecycles){ }; function getWindowWidth() { - return ( - window.innerWidth || - (document.documentElement && document.documentElement.clientWidth) || - (document.body && document.body.clientWidth) || - 0 - ); + return isBrowser ? document.documentElement.clientWidth : 0; } function getWindowHeight() { - return ( - window.innerHeight || - (document.documentElement && document.documentElement.clientHeight) || - (document.body && document.body.clientHeight) || - 0 - ); + return isBrowser ? document.documentElement.clientHeight : 0; } /** diff --git a/src/core/main.js b/src/core/main.js index c522fbf90a..29dc532cf9 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -148,19 +148,24 @@ class p5 { const blurHandler = () => { this.focused = false; }; - window.addEventListener('focus', focusHandler); - window.addEventListener('blur', blurHandler); - p5.lifecycleHooks.remove.push(function() { - window.removeEventListener('focus', focusHandler); - window.removeEventListener('blur', blurHandler); - }); - - // Initialization complete, start runtime - if (document.readyState === 'complete') { + + if(typeof window !== 'undefined'){ + window.addEventListener('focus', focusHandler); + window.addEventListener('blur', blurHandler); + p5.lifecycleHooks.remove.push(function() { + window.removeEventListener('focus', focusHandler); + window.removeEventListener('blur', blurHandler); + }); + + // Initialization complete, start runtime + if (document.readyState === 'complete') { + this.#_start(); + } else { + this._startListener = this.#_start.bind(this); + window.addEventListener('load', this._startListener, false); + } + }else{ this.#_start(); - } else { - this._startListener = this.#_start.bind(this); - window.addEventListener('load', this._startListener, false); } } @@ -237,11 +242,13 @@ class p5 { // Always create a default canvas. // Later on if the user calls createCanvas, this default one // will be replaced - this.createCanvas( - 100, - 100, - constants.P2D - ); + if(typeof window !== 'undefined'){ + this.createCanvas( + 100, + 100, + constants.P2D + ); + } // Record the time when setup starts. millis() will start at 0 within // setup, but this isn't documented, locked-in behavior yet. @@ -253,16 +260,18 @@ class p5 { } if (this.hitCriticalError) return; - const canvases = document.getElementsByTagName('canvas'); - for (const k of canvases) { - // Apply touchAction = 'none' to canvases to prevent scrolling - // when dragging on canvas elements - k.style.touchAction = 'none'; - - // unhide any hidden canvases that were created - if (k.dataset.hidden === 'true') { - k.style.visibility = ''; - delete k.dataset.hidden; + if(typeof document !== 'undefined'){ + const canvases = document.getElementsByTagName('canvas'); + for (const k of canvases) { + // Apply touchAction = 'none' to canvases to prevent scrolling + // when dragging on canvas elements + k.style.touchAction = 'none'; + + // unhide any hidden canvases that were created + if (k.dataset.hidden === 'true') { + k.style.visibility = ''; + delete k.dataset.hidden; + } } } @@ -330,9 +339,10 @@ class p5 { // get notified the next time the browser gives us // an opportunity to draw. if (this._loop) { - this._requestAnimId = window.requestAnimationFrame( - this._draw.bind(this) - ); + const boundDraw = this._draw.bind(this); + this._requestAnimId = typeof window !== 'undefined' ? + window.requestAnimationFrame(boundDraw) : + setImmediate(boundDraw); } } diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index 04caa1144d..1d5ebcc64d 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -72,7 +72,9 @@ class Renderer { this._pInst = pInst; this._isMainCanvas = isMainCanvas; this.pixels = []; - this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1; + this._pixelDensity = typeof window !== 'undefined' ? + Math.ceil(window.devicePixelRatio) : + 1; this.width = w; this.height = h; From c4700f1536cce0f48ebac52d60614c4d95225472 Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Wed, 24 Dec 2025 22:31:04 +0000 Subject: [PATCH 4/7] Guard against a few references to window --- src/core/init.js | 26 +++++++++++++++----------- src/type/lib/Typr.js | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/core/init.js b/src/core/init.js index 764437b1ff..0ba781e406 100644 --- a/src/core/init.js +++ b/src/core/init.js @@ -13,6 +13,7 @@ import { initialize as initTranslator } from './internationalization'; * @return {Undefined} */ export const _globalInit = () => { + if(typeof window === 'undefined') return; // Could have been any property defined within the p5 constructor. // If that property is already a part of the global object, // this code has already run before, likely due to a duplicate import @@ -40,17 +41,20 @@ export const _globalInit = () => { }; // make a promise that resolves when the document is ready -export const waitForDocumentReady = () => - new Promise((resolve, reject) => { - // if the page is ready, initialize p5 immediately - if (document.readyState === 'complete') { - resolve(); - // if the page is still loading, add an event listener - // and initialize p5 as soon as it finishes loading - } else { - window.addEventListener('load', resolve, false); - } - }); +export const waitForDocumentReady = () =>{ + if(typeof document !== 'undefined'){ + return new Promise((resolve, reject) => { + // if the page is ready, initialize p5 immediately + if (document.readyState === 'complete') { + resolve(); + // if the page is still loading, add an event listener + // and initialize p5 as soon as it finishes loading + } else { + window.addEventListener('load', resolve, false); + } + }); + } +}; // only load translations if we're using the full, un-minified library export const waitingForTranslator = diff --git a/src/type/lib/Typr.js b/src/type/lib/Typr.js index e81fcb58f1..ec7e94a5ec 100644 --- a/src/type/lib/Typr.js +++ b/src/type/lib/Typr.js @@ -323,7 +323,7 @@ Typr["B"] = { } return s; }, - _tdec: window["TextDecoder"] ? new window["TextDecoder"]() : null, + _tdec: globalThis["TextDecoder"] ? new globalThis["TextDecoder"]() : null, readUTF8: function (buff, p, l) { var tdec = Typr["B"]._tdec; if (tdec && p == 0 && l == buff.length) return tdec["decode"](buff); From 130d67f29d0dc0dec9dbcf631bb9f90aecd3414e Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Thu, 25 Dec 2025 15:13:04 +0000 Subject: [PATCH 5/7] Use ES6 import in visual report script --- visual-report.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/visual-report.js b/visual-report.js index 12e21b6261..bffc39dacb 100644 --- a/visual-report.js +++ b/visual-report.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; const SLASH_REGEX = /\//g; async function generateVisualReport() { @@ -425,4 +425,4 @@ if (require.main === module) { }); } -module.exports = { generateVisualReport }; \ No newline at end of file +export { generateVisualReport }; From b1dcca7a95de1cdbd9eea6751d62d23d28a7378b Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Thu, 25 Dec 2025 15:28:09 +0000 Subject: [PATCH 6/7] Update CI node version --- .github/workflows/ci-lint.yml | 4 ++-- .github/workflows/ci-test.yml | 4 ++-- .github/workflows/release-workflow-v2.yml | 2 +- .github/workflows/release-workflow.yml | 2 +- visual-report.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 9410a25a43..ea1eccbee2 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v1 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@v1 with: - node-version: 20.x + node-version: 22.x - name: Get node modules run: npm ci env: diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index fda1bb81d1..e5ceb912c1 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -24,10 +24,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.x + - name: Use Node.js 22.x uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 22.x - name: Verify Chrome (Ubuntu) if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome' diff --git a/.github/workflows/release-workflow-v2.yml b/.github/workflows/release-workflow-v2.yml index 907c64596a..6574cc0e88 100644 --- a/.github/workflows/release-workflow-v2.yml +++ b/.github/workflows/release-workflow-v2.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 22 - name: Get semver info id: semver uses: akshens/semver-tag@v4 diff --git a/.github/workflows/release-workflow.yml b/.github/workflows/release-workflow.yml index 6c68dd09ff..714f0890d0 100644 --- a/.github/workflows/release-workflow.yml +++ b/.github/workflows/release-workflow.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 22 - name: Get semver info id: semver uses: akshens/semver-tag@v4 diff --git a/visual-report.js b/visual-report.js index bffc39dacb..3a29e223cb 100644 --- a/visual-report.js +++ b/visual-report.js @@ -418,7 +418,7 @@ async function generateVisualReport() { } // Run the function if this script is executed directly -if (require.main === module) { +if (import.meta.main === true) { generateVisualReport().catch(error => { console.error('Failed to generate report:', error); process.exit(1); From b417ba994bcb9b176bb0d8854bd632c993c03fcf Mon Sep 17 00:00:00 2001 From: limzykenneth Date: Thu, 25 Dec 2025 15:39:04 +0000 Subject: [PATCH 7/7] Provide a node compatible export --- package.json | 4 ++++ src/app.node.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/app.node.js diff --git a/package.json b/package.json index 0815655f85..1e66029484 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,10 @@ "types": "./types/global.d.ts", "default": "./dist/app.js" }, + "./node": { + "types": "./types/p5.d.ts", + "default": "./dist/app.node.js" + }, "./core": { "default": "./dist/core/main.js" }, diff --git a/src/app.node.js b/src/app.node.js new file mode 100644 index 0000000000..ef270459b7 --- /dev/null +++ b/src/app.node.js @@ -0,0 +1,60 @@ +// core +import p5 from './core/main'; + +// shape +import shape from './shape'; +shape(p5); + +//accessibility +import accessibility from './accessibility'; +accessibility(p5); + +// color +import color from './color'; +color(p5); + +// core +// currently, it only contains the test for parameter validation +// import friendlyErrors from './core/friendly_errors'; +// friendlyErrors(p5); + +// data +import data from './data'; +data(p5); + +// DOM +import dom from './dom'; +dom(p5); + +// image +import image from './image'; +image(p5); + +// io +import io from './io'; +io(p5); + +// math +import math from './math'; +math(p5); + +// utilities +import utilities from './utilities'; +utilities(p5); + +// webgl +import webgl from './webgl'; +webgl(p5); + +// typography +import type from './type'; +type(p5); + +// Shaders + filters +import shader from './webgl/p5.Shader'; +p5.registerAddon(shader); +import strands from './strands/p5.strands'; +p5.registerAddon(strands); + +export default p5; +