diff --git a/package.json b/package.json index 16f60cae3e..63ff7b7711 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/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() 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; + diff --git a/src/core/environment.js b/src/core/environment.js index 6fffe81973..1358084798 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 = window.performance.now(); + 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 + }); + } } }; @@ -59,9 +62,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?' @@ -198,7 +201,7 @@ function environment(p5, fn, lifecycles){ * } * } */ - fn.focused = document.hasFocus(); + fn.focused = isBrowser ? document.hasFocus() : true; /** * Changes the cursor's appearance. @@ -580,7 +583,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. @@ -608,7 +611,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. @@ -734,21 +737,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; } /** @@ -808,7 +801,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 ( @@ -879,7 +871,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) { diff --git a/src/core/friendly_errors/fes_core.js b/src/core/friendly_errors/fes_core.js index 8962745918..fdc19f33da 100644 --- a/src/core/friendly_errors/fes_core.js +++ b/src/core/friendly_errors/fes_core.js @@ -26,7 +26,7 @@ import { translator } from '../internationalization'; import errorTable from './browser_errors'; import * as contants from '../constants'; -function fesCore(p5, fn){ +function fesCore(p5, fn, lifecycles){ // p5.js blue, p5.js orange, auto dark green; fallback p5.js darkened magenta // See testColors below for all the color codes and names const typeColors = ['#2D7BB6', '#EE9900', '#4DB200', '#C83C00']; @@ -972,9 +972,11 @@ function fesCore(p5, fn){ p5._fesLogger = null; p5._fesLogCache = {}; - window.addEventListener('load', checkForUserDefinedFunctions, false); - window.addEventListener('error', p5._fesErrorMonitor, false); - window.addEventListener('unhandledrejection', p5._fesErrorMonitor, false); + lifecycles.presetup = function () { + window.addEventListener('load', checkForUserDefinedFunctions, false); + window.addEventListener('error', p5._fesErrorMonitor, false); + window.addEventListener('unhandledrejection', p5._fesErrorMonitor, false); + }; /** * Prints out all the colors in the color pallete with white text. @@ -1134,7 +1136,7 @@ function fesCore(p5, fn){ // Exposing this primarily for unit testing. fn._helpForMisusedAtTopLevelCode = helpForMisusedAtTopLevelCode; - if (document.readyState !== 'complete') { + if (typeof document !== 'undefined' && document.readyState !== 'complete') { window.addEventListener('error', helpForMisusedAtTopLevelCode, false); // Our job is only to catch ReferenceErrors that are thrown when @@ -1150,5 +1152,5 @@ function fesCore(p5, fn){ export default fesCore; if (typeof p5 !== 'undefined') { - fesCore(p5, p5.prototype); + p5.registerAddon(fesCore); } diff --git a/src/core/friendly_errors/param_validator.js b/src/core/friendly_errors/param_validator.js index 5fa4e73151..ddb011a54a 100644 --- a/src/core/friendly_errors/param_validator.js +++ b/src/core/friendly_errors/param_validator.js @@ -618,5 +618,5 @@ function validateParams(p5, fn, lifecycles) { export default validateParams; if (typeof p5 !== 'undefined') { - validateParams(p5, p5.prototype); + p5.registerAddon(validateParams); } 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/core/main.js b/src/core/main.js index 4ce9d91c55..a0b501a531 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -50,7 +50,7 @@ class p5 { constructor(sketch, node) { // Apply addon defined decorations if(p5.decorations.size > 0){ - decorateClass(p5, p5.decorations); + decorateClass(p5, p5.decorations, 'p5'); p5.decorations.clear(); } @@ -125,19 +125,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); } } @@ -224,15 +229,17 @@ 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. - this._millisStart = window.performance.now(); + this._millisStart = globalThis.performance.now(); const context = this._isGlobal ? window : this; if (typeof context.setup === 'function') { @@ -240,21 +247,23 @@ 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; + } } } - 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(); @@ -265,7 +274,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' @@ -275,7 +284,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; @@ -317,9 +326,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); } } @@ -521,7 +531,6 @@ function createBindGlobal(instance) { // Generic function to decorate classes function decorateClass(Target, decorations, path){ - path ??= Target.name; // Static properties for(const key in Target){ if(!key.startsWith('_')){ diff --git a/src/core/p5.Renderer.js b/src/core/p5.Renderer.js index f9caac7793..8467695bf8 100644 --- a/src/core/p5.Renderer.js +++ b/src/core/p5.Renderer.js @@ -71,12 +71,14 @@ class Renderer { this._isMainCanvas = isMainCanvas; this.pixels = []; + const defaultRatio = typeof window !== 'undefined' ? + Math.ceil(window.devicePixelRatio) : + 1; if (isMainCanvas) { - this._pixelDensity = Math.ceil(window.devicePixelRatio) || 1; + this._pixelDensity = defaultRatio; } else { - const parentDensity = pInst._pInst?._renderer?._pixelDensity; - this._pixelDensity = parentDensity || Math.ceil(window.devicePixelRatio) || 1; + this._pixelDensity = parentDensity || defaultRatio; } this.width = w; diff --git a/src/events/acceleration.js b/src/events/acceleration.js index 7750f6099b..e788171abc 100644 --- a/src/events/acceleration.js +++ b/src/events/acceleration.js @@ -19,6 +19,10 @@ function acceleration(p5, fn, lifecycles){ signal: this._removeSignal }); } + + // Initialize device orientation value + this.deviceOrientation = typeof window !== 'undefined' && + window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait'; }; /** @@ -30,8 +34,7 @@ function acceleration(p5, fn, lifecycles){ * @property {(LANDSCAPE|PORTRAIT)} deviceOrientation * @readOnly */ - fn.deviceOrientation = - window.innerWidth / window.innerHeight > 1.0 ? 'landscape' : 'portrait'; + fn.deviceOrientation = 'landscape'; /** * The system variable accelerationX always contains the acceleration of the diff --git a/src/shape/2d_primitives.js b/src/shape/2d_primitives.js index aa246664b8..53738a4280 100644 --- a/src/shape/2d_primitives.js +++ b/src/shape/2d_primitives.js @@ -1079,7 +1079,6 @@ function primitives(p5, fn){ * rect(-20, -30, 55, 55); * } */ - /** * @method rect * @param {Number} x 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);