Feature: #97816 - TypoScript syntax improvements

See forge#97816

Description

TYPO3 v12 comes with a new TypoScript syntax parser that is more performant, more robust and allows better tooling in the Backend.

The new parser is more forgiving in many places, this documentation lists the new capabilities.

Also see Breaking: #97816 - TypoScript syntax changes for an overview of breaking syntax changes.

Impact

Improved comment parsing

TypoScript comment detection had various nasty quirks with the old parser. The confusing behavior did lead to many headaches in the past and not sticking to the weird parser restrictions in comment parsing could easily lead to unexpected results, often ignoring bigger sections of the subsequent TypoScript lines.

This has been relaxed heavily: Comment detection should almost always act as developers and integrators would expect from a language. Especially the former obligation to place a closing multiline comment (*/) on a single line to close the comment section has been removed.

A couple of examples to clarify:

foo # This is a comment to an invalid line

foo < bar // This is a comment
foo < bar /* This is a valid comment, too */

foo > # Another valid comment

foo := addToList(1) # Yes, a comment

[foo = bar] # Much comment. Much wow.

<INCLUDE_TYPOSCRIPT: source="..."> /* A comment */

foo (
  # This is NOT a comment but part of the value assignment!
  bar = barValue
) # This is a comment

foo = bar // This is NOT a comment but part of the value assignment!

@import in conditions

Placing an @import keyword within a condition is now supported, the example below works. Note that this obsoletes the clumsy <INCLUDE_TYPOSCRIPT: syntax, and integrators are encouraged to fully switch to @import.

[frontend.user.isLoggedIn]
  @import 'EXT:my_extension/Configuration/TypoScript/LoggedInUser.typoscript'
[ELSE]
  @import 'EXT:my_extension/Configuration/TypoScript/NotLoggedInUser.typoscript'
[END]

Scope restriction to file / snipped level

The old TypoScript parser merged the entire TypoScript for a page into one big chunk of text. The new parser does not do that anymore, but parses each included snippet one-by-one. This automatically means state no longer leaks to subsequent snippets. Missing closing brackets } in one file do not destroy block integrity of a following include anymore. Same for conditions: A missing closing condition block ([END] or [GLOBALS]) no longer leaks to another file - a conditions ends at the end of a file or snippet.

Nesting conditions is partially supported

Nesting conditions is partially possible with the new TypoScript parser, if the conditions are in different files. As example, let's first sort what happens when two conditions follow directly in one snippet:

[frontend.user.isLoggedIn]
  @import 'EXT:my_extension/Configuration/TypoScript/LoggedInUser.typoscript'
[applicationContext == "Development"]
  @import 'EXT:my_extension/Configuration/TypoScript/Development.typoscript'
[END]

This always worked and did not change with the new parser: Opening a new condition automatically closes the preceding one. In the example above, both conditions are standalone: Development.typoscript is included no matter if a user is logged in or not.

But, and this in new, nesting conditions within different files is possible now. In the example below, file LoggedInUserDevelopment.typoscript is only included if a user is logged in and the application is in development context.

[frontend.user.isLoggedIn]
  @import 'EXT:my_extension/Configuration/TypoScript/LoggedInUser.typoscript'
[END]

# File LoggedInUser.typoscript:
[applicationContext == "Development"]
  @import 'EXT:my_extension/Configuration/TypoScript/LoggedInUserDevelopment.typoscript'
[END]

Irrelevant order of <INCLUDE_TYPOSCRIPT: tokens

The <INCLUDE_TYPOSCRIPT: keywords has the three properties source, condition and extensions. They had to be in a specific order with the old parser, but can be placed in arbitrary order now.

Further clarifications

  • The "reference" =< operator is not a direct language construct. The parser understands the syntax, but does not resolve it. Allowed =< operator are very limited: In general, it can only be used for Frontend Content Objects, typically like this: tt_content.bullets =< lib.contentElement. Another usage is referencing lib.parseFunc. Using the =< operator in these cases can have a performance advantage since it avoids an expensive copy operation that is done lazily if really needed. There are two methods that resolve this operator, namely ContentObjectRenderer->cObjGetSingle() and ContentObjectRenderer->mergeTSRef(). This also means: The =< operator is not supported in TypoScript constants, is only supported for specific elements in TypoScript setup, and is not supported in TSconfig. Also note the reference operator does not support "relative" copies like the "copy" operator supports with 20 < .10 and similar.

  • The new parser has a minor change in behavior with the "copy" < operator on top-level. This shouldn't have huge impact in real life usage and is documented for completeness. Consider this example:

    lib.viewConfig {
      baz = bazValue
    }
    
    first = FLUIDTEMPLATE
    first < lib.viewConfig
    

    The situation is there that lib.viewConfig has no assigned value (just children). The target first however has value FLUIDTEMPLATE. The old TypoScript parser usually keeps the "target" value in such cases, but only if the TypoScript object is not on top level (first in contrast to first.10 or similar). In the example above, value FLUIDTEMPLATE would vanish with the old parser, but is now kept with the new parser.