How the OpenMage CssJsMinify Module Preserves JavaScript Defer and Async Attributes During Minification

The module preserves defer and async attributes by using a targeted regex pattern that captures the entire script tag suffix unchanged, modifying only the src URL while leaving all subsequent attributes intact.

The fballiano/openmage-cssjs-minify extension optimizes frontend performance by minifying CSS and JavaScript files on the fly. A critical requirement for any minification tool is to preserve JavaScript defer or async attributes during processing, ensuring that page loading behavior remains unchanged after optimization.

How the Observer Captures Script Attributes Without Parsing

The minification logic resides in app/code/community/Fballiano/CssjsMinify/Model/Observer.php, specifically within the httpResponseSendBefore observer method. Rather than parsing the DOM, the module employs a precise regular expression to isolate and replace only the file path while preserving the surrounding tag structure.

The Regex Pattern That Isolates the src Attribute

Lines 38-41 define the pattern that splits the script tag into three distinct capture groups:

$pattern = '/(<script.+src\s*=\s*["\'])(.*\.js)(["\'].*>)/iU';
  • $matches[1]: Contains everything from the opening <script through the src= attribute and its opening quote.
  • $matches[2]: Captures only the JavaScript file URL (the target for minification).
  • $matches[3]: Captures everything after the URL up to the closing >, including the closing quote of the src attribute, any whitespace, and all additional attributes like defer, async, type, or crossorigin.

Why $matches[3] Preserves defer and async

Because the regex captures the entire remainder of the opening tag in $matches[3], attributes that appear after the src property are not parsed or modified. They are treated as an opaque string that gets reattached verbatim during reconstruction. This design ensures that defer and async boolean attributes, which typically appear after the source path, survive the minification process automatically.

Replacement Logic That Leaves Attributes Intact

The actual substitution occurs in lines 62-66 of the same observer method:

$matches[2] = $minifiedUrl . $hash;   // replace only the URL
return $matches[1] . $matches[2] . $matches[3];

The callback generates a minified URL (appending a cache-busting hash), assigns it to $matches[2], and then concatenates all three groups back together. Since $matches[3] is returned unchanged, any defer or async attributes present in the original markup remain exactly as they were.

Code Example

Original HTML:

<script src="/js/app.js" defer async></script>

After processing by the CssJsMinify observer:

<script src="/media/fbminify/9c8e7a5b-1618923600.js" defer async></script>

The defer and async attributes persist because they are contained within $matches[3], which the replacement logic leaves untouched.

Summary

  • Regex-based isolation: The observer uses a capture group pattern in Observer.php (lines 38-41) to separate the src URL from the rest of the tag.
  • Selective replacement: Only the file path ($matches[2]) is modified to point to the minified version; $matches[3] is preserved verbatim.
  • Attribute preservation: Because defer, async, and other attributes reside in the preserved suffix group, they survive minification without explicit handling.
  • Zero DOM overhead: The string-based approach avoids DOM parsing overhead while maintaining full compatibility with standard script tag variations.

Frequently Asked Questions

Does the module support other script attributes like type or crossorigin?

Yes. Because the regex captures everything after the src URL into $matches[3], any attribute that appears after the source path—including type="module", crossorigin="anonymous", integrity hashes, or nomodule—is preserved unchanged during the minification process.

What happens if a script tag has no src attribute?

The regex pattern specifically looks for src\s*=\s*["\'] followed by .*\.js. Inline scripts without a src attribute or scripts with non-.js extensions will not match the pattern and will be left untouched by the observer, passing through to the browser unmodified.

Is the regex pattern case-sensitive for defer and async attributes?

No. The regex uses the i (case-insensitive) modifier (/iU), meaning it will match SCRIPT, SRC, Defer, or ASYNC regardless of case. However, the replacement logic preserves the original casing of $matches[3], so whatever case the original markup used for these attributes is maintained in the output.

The module processes CSS files in a similar manner using a separate regex pattern (lines 68-95 of Observer.php), but CSS <link> tags do not typically use defer or async attributes. The same suffix-preservation logic applies, meaning attributes like media="print" or crossorigin on link tags are preserved in the same way that defer and async are preserved on script tags.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →