Skip to content

Support/php 8.4#154

Merged
dconco merged 58 commits intomasterfrom
support/php-8.4
Mar 23, 2026
Merged

Support/php 8.4#154
dconco merged 58 commits intomasterfrom
support/php-8.4

Conversation

@dconco
Copy link
Copy Markdown
Owner

@dconco dconco commented Mar 23, 2026

No description provided.

dconco and others added 30 commits February 21, 2026 10:05
…ort, fixing deadlock issues and improving compression handling
…nifier, add methods for custom compressor path and force native compression
actions-user and others added 25 commits February 25, 2026 20:34
…pdate test method names for clarity and consistency
…cking

- Removed the executedScripts tracking from RuntimeManager to simplify script execution logic.
- Updated script execution methods to directly append and remove script elements without checking for duplicates.
- Cleaned up related comments and adjusted method calls in AppManager to reflect the removal of executed scripts cache.
- Enhanced logging for script execution to aid in debugging and monitoring.
- Eliminated console.log statements related to script execution, caching, and loading in the RuntimeManager class across multiple script files.
- This change aims to clean up the code and reduce unnecessary logging during script operations.
- Eliminated console logging related to script execution and DOM updates in the RuntimeManager and navigateHistory files.
- This change aims to clean up the code and reduce unnecessary logging during script operations and history navigation.
- Removed inline script execution from `runScriptsForElement`.
- Updated `runPhpSpaScripts` to handle script attributes more efficiently.
- Improved cache handling for script execution, ensuring attributes are preserved.
- Simplified the logic for creating and executing new script elements.
…style-preload behavior

- add RuntimeConfig with preserveUpdatedHtmlState and waitForStyles
- persist current target HTML into history before navigation when enabled
- expose phpspa.config(...) for runtime client configuration
- document both options in CHANGELOG v2.0.9
- Added support for 'popstate' event in RuntimeManager with associated payload structure.
- Implemented `off` and `resetEvents` methods in RuntimeManager to manage event listeners effectively.
- Updated AppManager to include `off` and `resetEvents` methods for better event handling.
- Modified navigateHistory function to utilize the new popstate event structure and prevent default behavior.
- Refactored EventObject and EventPayload types to accommodate new event payloads and improve type safety.
- Updated TypeScript definitions to reflect changes in event handling and payload structures.
Updated image source URL in README for PhpSPA.
Copilot AI review requested due to automatic review settings March 23, 2026 21:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates PhpSPA to better support PHP 8.4 and expands both server-side and client-side runtime capabilities (native compression options, asset URL routing changes, and richer client runtime configuration/events).

Changes:

  • Adds native compressor configuration options (custom library path, force native mode, optional esbuild JS minification) and introduces binary response compression negotiation (zstd/br/gzip).
  • Updates client runtime to add configurable navigation/style-loading behavior and new popstate lifecycle with listener management.
  • Revises asset URL structure (nested folders) and updates related tests/types/docs/build pipeline outputs accordingly.

Reviewed changes

Copilot reviewed 34 out of 46 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Unit/Core/Utils/HtmlCompressorTest.php Updates unit test to use new binary compression API.
tests/Unit/Core/Helper/AssetLinkManagerTest.php Adjusts regex expectations for new nested asset URL structure.
tests/AssetLinkTest.php Updates integration-style regex checks for nested asset URLs.
src/script/types/utils/preloadStylesFromContent.d.ts.map Regenerated TS declaration map after runtime/style loader changes.
src/script/types/types/RuntimeInterfaces.d.ts.map Regenerated TS declaration map after runtime event/config typing changes.
src/script/types/types/RuntimeInterfaces.d.ts Adds typed event payload map, runtime config, and popstate payload typing.
src/script/types/helpers/navigateHistrory.d.ts.map Regenerated declaration map after popstate handling changes.
src/script/types/core/RuntimeManager.d.ts.map Regenerated declaration map after runtime API typing changes.
src/script/types/core/RuntimeManager.d.ts Updates runtime API typings (config, typed emit/off/reset, etc.).
src/script/types/core/AppManager.d.ts.map Regenerated declaration map after AppManager API changes.
src/script/types/core/AppManager.d.ts Updates AppManager typings for config + typed event on/off/reset.
src/script/phpspa.mjs Updates distributed client runtime (config, popstate event, style handling, script execution changes).
src/script/phpspa.min.js Updates minified distributed runtime bundle to match new runtime behavior.
src/script/phpspa.js Updates UMD distributed runtime bundle to match new runtime behavior.
src/script/phpspa.cjs Updates CJS distributed runtime bundle to match new runtime behavior.
src/script/FFIBridge.cpp Adds new FFI export to compress with scope/debug support (esbuild-enabled path).
src/compression/functions/minifyJS.cpp Introduces esbuild-based JS minification path (temp files + command execution) with fallback.
src/compression/HtmlCompressor.h Extends JS minifier API to accept scope/debug output.
package.json Bumps npm package version.
lib/utils/preloadStylesFromContent.ts Changes stylesheet preloading to regex-based extraction + stabilized URL resolution.
lib/types/RuntimeInterfaces.ts Adds typed event payload map + runtime config types.
lib/helpers/navigateHistrory.ts Adds cancelable popstate event and adjusts DOM restore path.
lib/core/RuntimeManager.ts Adds runtime config/events API updates and changes script execution strategy.
lib/core/AppManager.ts Adds runtime config forwarding, DOM-state snapshot option, and event off/reset methods.
examples/index.php Updates example to use new native compression configuration API and script naming.
examples/components/Counter.php Updates component script registration to include a name/id.
docs/performance/html-compression.md Documents new native compressor/esbuild configuration features.
composer.json Changes local dev server port for composer start.
badge/loc.svg Updates LOC badge value.
app/interfaces/ApplicationContract.php Adds new public API methods for compressor configuration.
app/core/Utils/Timer.php Adds timing utility used for compression timing/logging.
app/core/Utils/HtmlCompressor.php Adds scope/esbuild options and replaces gzip-only with negotiated binary compression.
app/core/Router/PrefixRouter.php Formatting/tidy updates and minor return expression simplification.
app/core/Impl/RealImpl/AppImpl.php Wires new compressor configuration + scope-aware asset compression; adds compression timing logs.
app/core/Http/HttpRequest.php Refactors header retrieval + signature; formatting updates.
app/core/Helper/AssetLinkManager.php Changes asset URLs to use nested folders and updates request parsing accordingly.
app/core/Compression/NativeCompressor.php Extends native compressor invocation for scope/esbuild + debug output handling.
app/client/Http/Request.php Updates request interface header() signature + docs; formatting changes.
app/client/Compression/Compressor.php Adds typed class constants for PHP 8.4+.
app/client/App.php Updates phpdoc @method surface to include new ApplicationContract APIs.
README.md Updates hero image URL.
CHANGELOG.md Adds development changelog section describing new compression/runtime features.
.vscode/settings.json Updates editor defaults (font size, formatOnSave).
.github/workflows/build-compressor.yml Adds WSL build artifact and adjusts packaging/release attachment steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 169 to 171
public static runScriptsForElement(element: HTMLElement): void {
this.runInlineScripts(element)
this.runPhpSpaScripts(element)
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runScriptsForElement() no longer calls runInlineScripts(), but runInlineScripts() remains in the class. This makes inline-script handling behavior harder to reason about and leaves dead code behind. Either remove runInlineScripts() or call it from runScriptsForElement() (and keep responsibilities between inline scripts vs PhpSPA-managed scripts clearly separated).

Copilot uses AI. Check for mistakes.
Comment on lines +232 to 305
private static runPhpSpaScripts(container: HTMLElement) {
const scripts = container.querySelectorAll("phpspa-script, script") as NodeListOf<HTMLScriptElement>
const nonce = document.head.getAttribute('x-phpspa')

scripts.forEach(async (script: HTMLScriptElement): Promise<void> => {
const scriptUrl = script.getAttribute('src')
const typeAttr = (script.getAttribute('type') ?? '').trim().toLowerCase()
const isModule = typeAttr === 'module'
const isExecutable =
typeAttr === '' ||
typeAttr === 'text/javascript' ||
typeAttr === 'application/javascript' ||
typeAttr === 'application/ecmascript' ||
typeAttr === 'text/ecmascript' ||
isModule


if (!isExecutable) {
const newScript = document.createElement("script")

newScript.nonce = nonce ?? undefined;
for (const attribute of Array.from(script.attributes)) {
newScript.setAttribute(attribute.name, attribute.value)
}
newScript.textContent = script.textContent

// --- Copy all attributes except the data-type identifier ---
if (!newScript.getAttribute('nonce')) newScript.nonce = nonce ?? undefined

// --- Execute and immediately remove from DOM ---
return document.head.appendChild(newScript).remove()
}

if (!scriptUrl) {
// --- Create new script element ---
const newScript = document.createElement("script")

// --- Copy all attributes ---
for (const attribute of Array.from(script.attributes)) {
newScript.setAttribute(attribute.name, attribute.value)
}

if (!newScript.getAttribute('nonce')) newScript.nonce = nonce ?? undefined

newScript.textContent = script.textContent

// --- Execute and immediately remove from DOM ---
document.head.appendChild(newScript).remove()
return document.head.appendChild(newScript).remove()
}
})
}

// --- Check cache first, then execute the cached content else download the script ---
if (this.ScriptsCachedContent[scriptUrl]) {
const newScript = document.createElement("script")

private static runPhpSpaScripts(container: HTMLElement) {
const scripts = container.querySelectorAll("phpspa-script, script[data-type=\"phpspa/script\"]") as NodeListOf<HTMLScriptElement>
const nonce = document.head.getAttribute('x-phpspa')
newScript.textContent = this.ScriptsCachedContent[scriptUrl]

scripts.forEach(async (script: HTMLScriptElement): Promise<void> => {
const scriptUrl = script.getAttribute('src') ?? ''
const scriptType = script.getAttribute('type') ?? ''

// --- Skip if this script has already been executed ---
if (!this.executedScripts.has(scriptUrl)) {
this.executedScripts.add(scriptUrl);

// --- Check cache first ---
if (this.ScriptsCachedContent[scriptUrl]) {
const newScript = document.createElement("script");
newScript.textContent = `(()=>{\n${this.ScriptsCachedContent[scriptUrl]}\n})()`;
newScript.nonce = nonce ?? undefined;
newScript.type = scriptType;

// --- Execute and immediately remove from DOM ---
document.head.appendChild(newScript).remove();
return;
for (const attribute of Array.from(script.attributes)) {
if (attribute.name == 'src') continue
newScript.setAttribute(attribute.name, attribute.value)
}

const response = await fetch(scriptUrl, {
headers: {
"X-Requested-With": "PHPSPA_REQUEST_SCRIPT",
}
})
if (!newScript.getAttribute('nonce')) newScript.nonce = nonce ?? undefined

if (response.ok) {
const scriptContent = await response.text()
// --- Execute and immediately remove from DOM ---
return document.head.appendChild(newScript).remove()
}

// --- Create new script element ---
const newScript = document.createElement("script")
newScript.textContent = `(()=>{\n${scriptContent}\n})()`;
newScript.nonce = nonce ?? undefined;
newScript.type = scriptType;
const response = await fetch(scriptUrl, {
headers: {
"X-Requested-With": "PHPSPA_REQUEST_SCRIPT",
}
})

if (response.ok) {
const scriptContent = await response.text()

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runPhpSpaScripts() now handles all <script> tags (including ones with src) by fetching the URL and executing it as an inline script (copying attributes except src). This changes browser semantics for external/module scripts (e.g., import.meta.url, module resolution/base URL, CSP/SRI behavior, and caching), and can break scripts that expect to be loaded via src. Consider limiting this fetch+inline path to phpspa-script / script[data-type="phpspa/script"], and for regular <script src> prefer injecting a new <script src> element (optionally with dedupe) instead of inlining fetched content.

Copilot uses AI. Check for mistakes.
@dconco dconco merged commit bbf9f01 into master Mar 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants