diff --git a/package.json b/package.json index 86cfd9ef3b..1af1454475 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,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 852d21417b..304809f220 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. @@ -570,7 +573,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. @@ -598,7 +601,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. @@ -724,21 +727,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; } /** @@ -794,7 +787,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 ( @@ -865,7 +857,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/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 22d715a9b3..fe62fe55b2 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,15 +242,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') { @@ -253,21 +260,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(); @@ -278,7 +287,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 +297,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; @@ -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; 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);