.. include:: /Includes.rst.txt .. _asset-management: ================ Asset Management ================ The Handlebars extension integrates with TYPO3's modern AssetCollector API to manage JavaScript and CSS assets in your frontend rendering. This feature allows you to register external files and inline code directly through TypoScript configuration. .. contents:: Table of Contents :depth: 2 :local: Overview ======== TYPO3 13+ provides the :php:`AssetCollector` service as the recommended way to register frontend assets. The Handlebars extension fully supports this API through the :typoscript:`assets` configuration in :typoscript:`HANDLEBARSTEMPLATE` content objects. Benefits -------- * **Deduplication**: Assets with the same identifier are automatically merged across the page * **Priority Control**: Control rendering order with priority options * **CSP Support**: Automatic nonce injection for Content Security Policy compliance * **Modern API**: Uses TYPO3's recommended approach (not deprecated PageRenderer methods) Asset Types =========== The AssetCollector API supports four distinct asset types, all fully supported by this extension: 1. **External JavaScript Files** - Link to external .js files 2. **Inline JavaScript Code** - Embed JavaScript directly in the page 3. **External CSS Files** - Link to external .css files 4. **Inline CSS Code** - Embed styles directly in the page JavaScript Files ---------------- Register external JavaScript files using the :typoscript:`javaScript` configuration: .. code-block:: typoscript 10 = HANDLEBARSTEMPLATE 10 { templateName = MyTemplate assets { javaScript { my-app-script { source = EXT:myext/Resources/Public/JavaScript/app.js attributes { async = 1 defer = 1 crossorigin = anonymous } options { priority = 1 useNonce = 1 } } } } } Inline JavaScript ----------------- Add inline JavaScript code using :typoscript:`inlineJavaScript`: .. code-block:: typoscript assets { inlineJavaScript { my-inline-script { source = console.log('Hello from Handlebars'); initMyApp(); attributes { type = module } options { priority = 1 } } } } CSS Files --------- Register external stylesheets using the :typoscript:`css` configuration: .. code-block:: typoscript assets { css { my-styles { source = EXT:myext/Resources/Public/Css/styles.css attributes { media = screen and (max-width: 768px) } options { priority = 1 } } } } Inline CSS ---------- Add inline styles using :typoscript:`inlineCss`: .. code-block:: typoscript assets { inlineCss { critical-css { source = body { margin: 0; padding: 0; } .container { max-width: 1200px; } } } } Configuration Reference ======================= source (required) ----------------- :aspect:`Type` string :aspect:`Description` Asset source. For external files, use :typoscript:`EXT:` syntax or absolute paths. For inline assets, provide the code directly as a string value. .. note:: The source must be a direct string value. Dynamic asset sources via stdWrap are not supported. Use fixed paths or inline code only. :aspect:`Example` .. code-block:: typoscript # External JavaScript file source = EXT:myext/Resources/Public/JavaScript/file.js # External CSS file source = EXT:myext/Resources/Public/Css/styles.css # Inline JavaScript code source = console.log('Hello'); # Inline CSS code source = body { margin: 0; } attributes ---------- :aspect:`Type` array :aspect:`Description` HTML attributes for the generated tag. Boolean attributes (:typoscript:`async`, :typoscript:`defer`, :typoscript:`disabled`) should be set to :typoscript:`1` to enable them. :aspect:`JavaScript Attributes` * :typoscript:`async` (boolean): Load script asynchronously * :typoscript:`defer` (boolean): Defer script execution * :typoscript:`nomodule` (boolean): Fallback for older browsers * :typoscript:`type` (string): Script type (e.g., "module") * :typoscript:`crossorigin` (string): CORS setting (e.g., "anonymous") * :typoscript:`integrity` (string): Subresource Integrity hash :aspect:`CSS Attributes` * :typoscript:`media` (string): Media query (e.g., "screen", "print") * :typoscript:`disabled` (boolean): Disable stylesheet * :typoscript:`title` (string): Stylesheet title * :typoscript:`crossorigin` (string): CORS setting * :typoscript:`integrity` (string): Subresource Integrity hash :aspect:`Example` .. code-block:: typoscript attributes { async = 1 defer = 1 type = module crossorigin = anonymous integrity = sha384-abc123def456 } options ------- :aspect:`Type` array :aspect:`Description` AssetCollector-specific options that control asset rendering behavior. :aspect:`Available Options` * :typoscript:`priority` (boolean): Render before other assets (default: 0) * :typoscript:`useNonce` (boolean): Add CSP nonce attribute (default: 0) :aspect:`Example` .. code-block:: typoscript options { priority = 1 useNonce = 1 } Complete Examples ================= Basic Example ------------- .. code-block:: typoscript page.20 = HANDLEBARSTEMPLATE page.20 { templateName = MyPage variables { title = TEXT title.data = page:title } assets { # External JavaScript javaScript { app-script { source = EXT:myext/Resources/Public/JavaScript/app.js attributes { defer = 1 } } } # External CSS css { main-styles { source = EXT:myext/Resources/Public/Css/main.css } } } } Advanced Example with Priority ------------------------------- .. code-block:: typoscript page.20 = HANDLEBARSTEMPLATE page.20 { templateName = MyPage assets { # High-priority critical CSS css { critical-styles { source = EXT:myext/Resources/Public/Css/critical.css options { priority = 1 } } } # Regular stylesheet with media query css { responsive-styles { source = EXT:myext/Resources/Public/Css/responsive.css attributes { media = screen and (min-width: 768px) } } } # Inline critical CSS (highest priority) inlineCss { above-the-fold { source = body { font-family: sans-serif; } h1 { font-size: 2em; } options { priority = 1 } } } # Modern JavaScript module javaScript { app-module { source = EXT:myext/Resources/Public/JavaScript/app.js attributes { type = module } options { useNonce = 1 } } } # Legacy fallback for older browsers javaScript { app-legacy { source = EXT:myext/Resources/Public/JavaScript/app.legacy.js attributes { nomodule = 1 defer = 1 } } } # Inline initialization code inlineJavaScript { app-init { source = window.APP_CONFIG = { apiUrl: '/api' }; options { priority = 1 } } } } } CDN Example with Security -------------------------- .. code-block:: typoscript assets { javaScript { cdn-library { source = https://cdn.example.com/library.js attributes { crossorigin = anonymous integrity = sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC defer = 1 } } } css { cdn-styles { source = https://cdn.example.com/styles.css attributes { crossorigin = anonymous integrity = sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN } } } } Migration from Legacy Methods ============================== If you're using the legacy :typoscript:`headerAssets` or :typoscript:`footerAssets` configuration, consider migrating to the modern :typoscript:`assets` approach: Before (Legacy) --------------- .. code-block:: typoscript 10 = HANDLEBARSTEMPLATE 10 { templateName = MyTemplate headerAssets = TEXT headerAssets.value = footerAssets = TEXT footerAssets.value = } After (Modern) -------------- .. code-block:: typoscript 10 = HANDLEBARSTEMPLATE 10 { templateName = MyTemplate assets { javaScript { my-script { source = EXT:myext/Resources/Public/JavaScript/script.js } } inlineJavaScript { footer-script { source = console.log('footer'); } } } } Benefits of Migration --------------------- * Automatic asset deduplication across multiple content elements * Better control over rendering order via priority * Content Security Policy nonce support * Type-safe attribute handling * Future-proof implementation using TYPO3's recommended API .. note:: Legacy methods (:typoscript:`headerAssets` and :typoscript:`footerAssets`) remain fully supported for backward compatibility. You can use both modern and legacy methods in the same content object. Best Practices ============== Unique Identifiers ------------------ Always use unique, namespaced identifiers across your entire page to avoid conflicts: .. code-block:: typoscript # Good: Namespaced identifier my-extension-app-script { source = ... } # Bad: Generic identifier (may conflict with other extensions) app { source = ... } Priority Management ------------------- Use :typoscript:`priority = 1` for assets that must load early: .. code-block:: typoscript # Critical inline CSS should have priority inlineCss { critical { source = ... options.priority = 1 } } # Regular stylesheets can have normal priority css { theme { source = ... # No priority option = loaded after priority assets } } Content Security Policy ----------------------- Enable :typoscript:`useNonce = 1` for inline scripts when using CSP: .. code-block:: typoscript inlineJavaScript { inline-config { source = window.config = { /* ... */ }; options.useNonce = 1 } } Module vs Classic Scripts ------------------------- Use :typoscript:`type = module` for ES6 modules and provide :typoscript:`nomodule` fallback for older browsers: .. code-block:: typoscript # Modern browsers javaScript { app-module { source = EXT:myext/Resources/Public/JavaScript/app.module.js attributes.type = module } } # Legacy browsers javaScript { app-legacy { source = EXT:myext/Resources/Public/JavaScript/app.legacy.js attributes.nomodule = 1 } } Troubleshooting =============== Assets Not Appearing -------------------- If assets don't appear in the rendered page: 1. **Check identifiers are unique** - Duplicate identifiers will cause the last one to win 2. **Verify source is not empty** - Empty sources will throw an exception 3. **Check for exceptions** - Configuration errors will halt rendering and display an error 4. **Ensure AssetCollector is initialized** - Only works in frontend context Duplicate Assets ---------------- If assets appear multiple times, check for: * Duplicate identifiers in different content objects * Multiple :typoscript:`HANDLEBARSTEMPLATE` objects registering the same asset Use unique, namespaced identifiers to prevent conflicts: .. code-block:: typoscript # Use extension prefix to avoid conflicts my-ext-my-script { source = ... } Configuration Errors -------------------- The extension throws :php:`InvalidAssetConfigurationException` for: * Unknown asset type (valid types: :typoscript:`javaScript`, :typoscript:`inlineJavaScript`, :typoscript:`css`, :typoscript:`inlineCss`) * Invalid asset type configuration (must be an array) * Invalid or empty identifier * Invalid asset configuration (must be an array) * Missing :typoscript:`source` parameter * Empty :typoscript:`source` value (after trimming whitespace) These errors will halt page rendering and display an exception message. Check your TypoScript configuration to resolve the issue. Boolean Attributes Not Working ------------------------------- Boolean attributes require the value :typoscript:`1` (or any truthy value) to be enabled: .. code-block:: typoscript # Correct attributes { async = 1 # Will output: async="async" defer = 1 # Will output: defer="defer" } # Incorrect - will not be output attributes { async = 0 # Omitted from output defer = # Omitted from output } See Also ======== * `TYPO3 AssetCollector Documentation `__ * `TYPO3 AssetCollector API `__ * :ref:`Using the Content Object Renderer `