Skip to content

[BUGFIX] TYPO3 v14: pre-resolve typo3_encore: TypoScript includes#1

Open
contemas-tschmidt wants to merge 2 commits into
dkd:typo3-14from
contemas-tschmidt:fix/v14-includecss-resolution
Open

[BUGFIX] TYPO3 v14: pre-resolve typo3_encore: TypoScript includes#1
contemas-tschmidt wants to merge 2 commits into
dkd:typo3-14from
contemas-tschmidt:fix/v14-includecss-resolution

Conversation

@contemas-tschmidt
Copy link
Copy Markdown

Summary

On TYPO3 v14, every page.includeCSS / includeJS* entry routed through the legacy
PageRendererHooks::renderPreProcess hook ends up with no <link>/<script> tag
on the FE.

Root cause

v14's RequestHandler::processHtmlBasedRenderingSettings was changed (vs v13) to
resolve every TS include via SystemResourceFactory before it reaches the
PageRenderer:

// vendor/typo3/cms-frontend/Classes/Http/RequestHandler.php:436+
foreach ($typoScriptPageArray['includeCSS.'] as $key => $cssResource) {
    ...
    try {
        $cssResource = $this->systemResourceFactory->createResource($cssResource);
    } catch (SystemResourceException) {
        continue;   // ← silently drops typo3_encore:foo entries
    }
    $this->pageRenderer->addCssFile($cssResource, ...);
}

typo3_encore:app doesn't match any registered SystemResource scheme, isn't a FAL
path, isn't a relative public file → CanNotResolveSystemResourceException
continue; and the entry is silently dropped. The legacy render-preProcess
PageRenderer hook never sees these entries because they never reach $cssFiles /
$jsLibs.

Even calling pageRenderer->addCssFile('typo3_encore:app') directly would fail in
v14 — addCssFile runs through SystemResourceFactory too (handleAddedResource()).

SystemResourceIdentifierFactory is final readonly with a hardcoded match
on the four built-in identifier types (PKG, FILE, URI, legacy EXT:), so registering
a custom typo3_encore: scheme isn't an option without core changes.

Fix

Add Classes/Integration/TypoScriptIncludesEventListener.php listening on
AfterTypoScriptDeterminedEvent. It:

  1. Walks the page TypoScript array (includeCSS. / includeCSSLibs. /
    includeJSlibs. / includeJSFooterlibs. / includeJS. / includeJSFooter.).
  2. For each typo3_encore:<entry> (or typo3_encore:<build>:<entry>) value,
    resolves the matching entrypoints.json and looks up the concrete CSS / JS
    file URLs.
  3. Calls the matching pageRenderer->addCssFile / addJsFile / addJsLibrary / addJsFooterFile / addJsFooterLibrary — those URLs are real public paths the
    v14 SystemResourceFactory accepts when later invoked by the PageRenderer
    (the eventual addCssFile resolution uses the relative-public-path branch
    and succeeds).

When v14's RequestHandler::processHtmlBasedRenderingSettings later tries to
process the same typo3_encore: TypoScript entries it still drops them
silently — but by then the actual encore assets are already registered with
the singleton PageRenderer.

Why settings are read from FrontendTypoScript directly

Extbase's ConfigurationManager is not yet request-bound at
AfterTypoScriptDeterminedEvent time ($GLOBALS['TYPO3_REQUEST'] is also unset
that early — see ApplicationType.php doc). Going through SettingsService
ConfigurationManager::getConfiguration() throws NoServerRequestGivenException.

Reading plugin.tx_typo3encore.settings.* directly off
FrontendTypoScript::getSetupArray() and constructing EntrypointLookups
manually side-steps the Extbase request dependency entirely.

Multi-file entries

Webpack entries can split into multiple files (runtime.js + app.js). PageRenderer
keys jsLibs by $name and silently drops duplicates. Each file is registered
under basename($file) as a unique name — the same trick the existing
TagRenderer uses.

Gating

The listener is registered only on Typo3Version::getMajorVersion() >= 14. v13
keeps the legacy render-preProcess path because v13's RequestHandler doesn't
filter through SystemResourceFactory.

Test plan

  • Verified on a TYPO3 v14.3 site: with dev-typo3-14, FE <head> had no
    <link>/<script> tags. With this patch, app.css + runtime.js +
    app.js show up as expected.
  • Multi-file entry (runtime.js + app.js for one entrypoint) now emits
    both, not just the first.
  • media="print" from includeCSS.app.media = print is honoured.
  • Tests: existing test suite passes locally; a unit test for the new
    listener would be nice but the AfterTypoScriptDeterminedEvent is hard to
    mock cleanly — open to suggestions.
  • v13 regression: not yet re-tested locally on v13 (gating should keep the
    legacy path intact, but a maintainer with v13 setup should sanity-check).

🤖 Generated with Claude Code

websi and others added 2 commits April 14, 2026 11:15
Remove TYPO3 12 compatibility
In TYPO3 v14, RequestHandler::processHtmlBasedRenderingSettings runs
every page.includeCSS / includeJS* entry through SystemResourceFactory
before forwarding to the PageRenderer. The factory rejects
"typo3_encore:<entry>" pseudo-paths as un-resolvable and the entry is
silently dropped (catch SystemResourceException -> continue;). Even
calling pageRenderer->addCssFile('typo3_encore:app') directly fails for
the same reason.

The legacy render-preProcess PageRenderer hook (PageRendererHooks)
therefore never sees these entries on v14 because they never reach
->cssFiles / ->jsLibs. End result: zero <link>/<script> tags
on the FE for any TS-driven encore include. (Fluid templates that
already use <e:renderWebpackLinkTags/> directly keep working — they
bypass TypoScript.)

Fix: add an AfterTypoScriptDeterminedEvent listener that walks the
page TypoScript array, resolves each "typo3_encore:" entry through
entrypoints.json, and calls pageRenderer's addCssFile / addJsFile /
addJsLibrary / addJsFooterFile / addJsFooterLibrary with the concrete
file paths the SystemResourceFactory accepts. Settings are read
directly from the FrontendTypoScript setup array because Extbase's
ConfigurationManager is not yet request-bound at this point in the
middleware chain (SettingsService -> ConfigurationManager would throw
NoServerRequestGivenException).

Multiple files inside one entry get distinct addJs*Library names via
basename() — the existing TagRenderer uses the same pattern.
Without this, runtime.js + entry.js collide on a single jsLibs key
and only the first survives.

Listener is gated behind Typo3Version::getMajorVersion() >= 14 so v13
keeps using the legacy render-preProcess hook (which v13's
RequestHandler still feeds through addCssFile without
SystemResourceFactory pre-filtering).
@websi websi force-pushed the typo3-14 branch 3 times, most recently from 08e36b9 to bf9d371 Compare May 4, 2026 07:30
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.

2 participants