diff --git a/package-lock.json b/package-lock.json index 0dfdc97ec..fe96b75ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,8 @@ "@enonic/react4xp": "^7.0.0", "@jest/globals": "^30.2.0", "@jest/types": "^30.4.1", - "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-node-resolve": "^16.0.3", - "@sinonjs/text-encoding": "^0.7.3", "@swc/core": "^1.15.47", "@types/jest": "^30.0.0", "@types/react": "^19.2.18", @@ -1986,31 +1984,34 @@ } }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -4110,23 +4111,6 @@ "url": "https://opencollective.com/pkgr" } }, - "node_modules/@rollup/plugin-alias": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-6.0.0.tgz", - "integrity": "sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==", - "dev": true, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "rollup": ">=4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "29.0.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.3.tgz", @@ -4292,9 +4276,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4309,9 +4290,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4326,9 +4304,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4343,9 +4318,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4360,9 +4332,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4377,9 +4346,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4394,9 +4360,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4411,9 +4374,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4428,9 +4388,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4445,9 +4402,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4462,9 +4416,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4479,9 +4430,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4496,9 +4444,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4708,6 +4653,40 @@ "@napi-rs/wasm-runtime": "1.1.4" } }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rspack/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rspack/binding-win32-arm64-msvc": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.8.tgz", @@ -4830,13 +4809,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "deprecated": "Deprecated: no longer maintained and no longer used by Sinon packages. See\n https://github.com/sinonjs/nise/issues/243 for replacement details.", - "dev": true - }, "node_modules/@swc/core": { "version": "1.15.47", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.47.tgz", @@ -4937,9 +4909,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4957,9 +4926,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4977,9 +4943,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4997,9 +4960,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5017,9 +4977,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -5037,9 +4994,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0 AND MIT", "optional": true, "os": [ diff --git a/package.json b/package.json index 43ffaca0a..8313820ff 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,8 @@ "@enonic/react4xp": "^7.0.0", "@jest/globals": "^30.2.0", "@jest/types": "^30.4.1", - "@rollup/plugin-alias": "^6.0.0", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-node-resolve": "^16.0.3", - "@sinonjs/text-encoding": "^0.7.3", "@swc/core": "^1.15.47", "@types/jest": "^30.0.0", "@types/react": "^19.2.18", diff --git a/rollup.config.nodePolyfills.mjs b/rollup.config.nodePolyfills.mjs index bade5606a..719db7979 100644 --- a/rollup.config.nodePolyfills.mjs +++ b/rollup.config.nodePolyfills.mjs @@ -1,7 +1,6 @@ -import alias from '@rollup/plugin-alias'; import commonjs from '@rollup/plugin-commonjs'; import nodeResolve from '@rollup/plugin-node-resolve'; -import {dirname, join} from 'path'; +import {dirname} from 'path'; import esbuild from 'rollup-plugin-esbuild' import {fileURLToPath} from 'url'; @@ -20,11 +19,6 @@ export default { }, // Some hooks are run in parallel but others, like the transform hook notably, are run in sequence, and the hooks are passed the result of the previous one. plugins: [ - alias({ - entries: [ - { find: '@sinonjs/text-encoding', replacement: join(__dirname,'./node_modules/@sinonjs/text-encoding/lib/encoding.js') }, - ] - }), nodeResolve(), // Resolves modules from node_modules (e.g. core-js internals) commonjs(), // A Rollup plugin to convert CommonJS modules to ES6, so they can be included in a Rollup bundle esbuild({ diff --git a/src/main/java/com/enonic/lib/react4xp/ssr/engine/EngineFactory.java b/src/main/java/com/enonic/lib/react4xp/ssr/engine/EngineFactory.java index 72e568355..3ab999e99 100644 --- a/src/main/java/com/enonic/lib/react4xp/ssr/engine/EngineFactory.java +++ b/src/main/java/com/enonic/lib/react4xp/ssr/engine/EngineFactory.java @@ -40,6 +40,7 @@ public static Context buildContext( final Engine engine ) .engine( engine ) .allowExperimentalOptions( true ) .allowHostAccess( HostAccess.NONE ) + .option( "js.text-encoding", "true" ) .build(); } finally diff --git a/src/main/java/com/enonic/lib/react4xp/ssr/renderer/Renderer.java b/src/main/java/com/enonic/lib/react4xp/ssr/renderer/Renderer.java index e1fd03194..969909283 100644 --- a/src/main/java/com/enonic/lib/react4xp/ssr/renderer/Renderer.java +++ b/src/main/java/com/enonic/lib/react4xp/ssr/renderer/Renderer.java @@ -28,8 +28,6 @@ public class Renderer public static final String KEY_HTML = "html"; - private static final String POLYFILL_BASICS_FILE = "/lib/enonic/polyfill-react4xp/polyfillBasics.js"; - private static final String POLYFILL_REACT4XP_NODE_FILE = "/lib/enonic/polyfill-react4xp/nodePolyfills.js"; private final long id; @@ -57,7 +55,6 @@ public Renderer( final ResourceReader resourceReader, final Config config, final LOG.debug( "#{}:{} loading polyfills ...", this.id, this.libraryName ); - this.assetLoader.loadAssetIntoEngine( POLYFILL_BASICS_FILE, true ); this.assetLoader.loadAssetIntoEngine( POLYFILL_REACT4XP_NODE_FILE, true ); LOG.debug( "#{}:{} loading globals...", this.id, this.libraryName ); diff --git a/src/main/resources/lib/enonic/polyfill-react4xp/nodePolyfills.ts b/src/main/resources/lib/enonic/polyfill-react4xp/nodePolyfills.ts index c82c3a769..985161204 100644 --- a/src/main/resources/lib/enonic/polyfill-react4xp/nodePolyfills.ts +++ b/src/main/resources/lib/enonic/polyfill-react4xp/nodePolyfills.ts @@ -1,9 +1,3 @@ -import type {ContextWithTimers} from './timers'; -import {polyfillTimers} from './timers'; - -// @ts-ignore Could not find a declaration file for module '@sinonjs/text-encoding' -import {TextEncoder} from '@sinonjs/text-encoding'; - // Spec-compliant atob/btoa polyfills from core-js. Side-effect imports — they install // globalThis.atob / globalThis.btoa if not already present. import 'core-js/modules/web.atob.js'; @@ -11,15 +5,20 @@ import 'core-js/modules/web.btoa.js'; (function (context) { //────────────────────────────────────────────────────────────────────────── - // Timers: When doing SSR it makes no sense to do anything asyncronously. + // Timers: When doing SSR it makes no sense to do anything asynchronously. + // Inert shims — callbacks are never invoked. //────────────────────────────────────────────────────────────────────────── - polyfillTimers(context); + context['setTimeout'] ??= () => 0; + context['setInterval'] ??= () => 0; + context['clearTimeout'] ??= () => undefined; + context['clearInterval'] ??= () => undefined; + context['queueMicrotask'] ??= () => undefined; //────────────────────────────────────────────────────────────────────────── - // text-encoding + // process: GraalJS doesn't provide a `process` global. Many libraries reach + // for `process.env.NODE_ENV` at module-load time, so a minimal shim avoids + // ReferenceError. //────────────────────────────────────────────────────────────────────────── - if (typeof context['TextEncoder'] === 'undefined') { - context['TextEncoder'] = TextEncoder; - } + context['process'] ??= {env: {}}; //@ts-expect-error TS2695: Left side of comma operator is unused and has no side effects. -})((1, eval)('this') as Partial); +})((1, eval)('this') as Record); diff --git a/src/main/resources/lib/enonic/polyfill-react4xp/polyfillBasics.js b/src/main/resources/lib/enonic/polyfill-react4xp/polyfillBasics.js deleted file mode 100644 index fa53ec9dd..000000000 --- a/src/main/resources/lib/enonic/polyfill-react4xp/polyfillBasics.js +++ /dev/null @@ -1,8 +0,0 @@ -// GraalJS doesn't provide a `process` global. Many libraries reach for `process.env.NODE_ENV` -// at module-load time, so a minimal shim avoids ReferenceError. -if (typeof process === 'undefined') { var process = { env: {} }; } - -// GraalJS has no `console`. Map the common methods to its built-in `print`. -if (typeof console === 'undefined') { - var console = { debug: print, log: print, warn: print, error: print }; -} diff --git a/src/main/resources/lib/enonic/polyfill-react4xp/timers.ts b/src/main/resources/lib/enonic/polyfill-react4xp/timers.ts deleted file mode 100644 index 225e1021d..000000000 --- a/src/main/resources/lib/enonic/polyfill-react4xp/timers.ts +++ /dev/null @@ -1,69 +0,0 @@ -type Brand< - Base, - Branding -> = Base & { - '__type__': Branding -}; - -// The returntype for window.setTimeout (in the browser) is number -export type TimerHandle = Brand - -export interface ContextWithTimers { - clearInterval: (handle: TimerHandle) => void - clearTimeout: (handle: TimerHandle) => void - queueMicrotask: (callback: () => void) => void - setInterval:( - callback: (args: void) => void, - delay?: number - ) => number - setTimeout: ( - callback: (args: void) => void, - delay?: number - ) => number - [key: string]: unknown -} - -//@ts-expect-error TS6133: 'handle' is declared but its value is never read. -export function clearInterval(handle: TimerHandle) { - return undefined; -} - -//@ts-expect-error TS6133: 'handle' is declared but its value is never read. -export function clearTimeout(handle: TimerHandle) { - return undefined -} - -export function queueMicrotask( - //@ts-expect-error TS6133: 'callback' is declared but its value is never read. - callback: () => void -) { - return undefined as void; -} - -export function setInterval( - //@ts-expect-error TS6133: 'callback' is declared but its value is never read. - callback: (args: void) => void, - //@ts-expect-error TS6133: 'delay' is declared but its value is never read. - delay?: number -) { - return 0 as TimerHandle; -} - -export function setTimeout( - //@ts-expect-error TS6133: 'callback' is declared but its value is never read. - callback: (args: void) => void, - //@ts-expect-error TS6133: 'delay' is declared but its value is never read. - delay?: number -) { - return 0 as TimerHandle; -} - -export function polyfillTimers< - T extends Partial = Partial ->(context: T) { - if (typeof context.clearInterval === 'undefined') { context.clearInterval = clearInterval; } - if (typeof context.clearTimeout === 'undefined') { context.clearTimeout = clearTimeout; } - if (typeof context.queueMicrotask === 'undefined') { context.queueMicrotask = queueMicrotask; } - if (typeof context.setInterval === 'undefined') { context.setInterval = setInterval; } - if (typeof context.setTimeout === 'undefined') { context.setTimeout = setTimeout; } -} diff --git a/src/test/java/com/enonic/lib/react4xp/ssr/renderer/RendererTest.java b/src/test/java/com/enonic/lib/react4xp/ssr/renderer/RendererTest.java index c86e1a0fb..c3fbda0c2 100644 --- a/src/test/java/com/enonic/lib/react4xp/ssr/renderer/RendererTest.java +++ b/src/test/java/com/enonic/lib/react4xp/ssr/renderer/RendererTest.java @@ -30,8 +30,6 @@ @EnabledIf("graalJsExists") class RendererTest { - private static final String POLYFILL_BASICS = "/lib/enonic/polyfill-react4xp/polyfillBasics.js"; - private static final String POLYFILL_NODE = "/lib/enonic/polyfill-react4xp/nodePolyfills.js"; private static final String CHUNKS_GLOBALS_JSON = "/react4xp/chunks.globals.json"; @@ -112,10 +110,7 @@ void render_real_react_component_via_server_edge() { final String reactBundle = readClasspathResource( "/test-fixtures/react-bundle.js" ); - // Use the real production-compiled polyfills, so the React bundle finds globalThis.TextEncoder - // (and the SSR timer no-ops) the same way it does in production. final Map fixtures = new HashMap<>(); - fixtures.put( POLYFILL_BASICS, readClasspathResource( POLYFILL_BASICS ) ); fixtures.put( POLYFILL_NODE, readClasspathResource( POLYFILL_NODE ) ); fixtures.put( CHUNKS_GLOBALS_JSON, "{ \"main\": { \"js\": \"globals.js\" } }" ); fixtures.put( GLOBALS_JS, @@ -184,7 +179,6 @@ private static String readClasspathResource( final String path ) private static Map baseFixtures() { final Map fixtures = new HashMap<>(); - fixtures.put( POLYFILL_BASICS, "" ); fixtures.put( POLYFILL_NODE, "" ); return fixtures; } diff --git a/src/test/js/react-bundle-entry.js b/src/test/js/react-bundle-entry.js index 950fdd4de..8560230de 100644 --- a/src/test/js/react-bundle-entry.js +++ b/src/test/js/react-bundle-entry.js @@ -1,10 +1,9 @@ // Bundles real React 19 + react-dom/server.edge for the GraalJS integration test. -// react-dom/server.edge has no MessageChannel/setImmediate dependency (verified by grep) — +// react-dom/server.edge has no MessageChannel/setImmediate dependency - // it's the same path the React team recommends in https://github.com/facebook/react/issues/31827. // // This bundle assumes the production-compiled nodePolyfills.js has already been loaded into -// the GraalJS context — it provides the TextEncoder global that react-dom-server.edge looks up -// at module-load time, plus the SSR timer no-ops. +// the GraalJS context const React = require('react'); const ReactDOMServer = require('react-dom/server.edge');