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

> Learn how the OpenMage CssJsMinify module protects your JavaScript defer and async attributes. Our targeted regex ensures these crucial attributes remain untouched during minification preserving script loading behavior.

- Repository: [Fabrizio Balliano/openmage-cssjs-minify](https://github.com/fballiano/openmage-cssjs-minify)
- Tags: internals
- Published: 2026-03-01

---

**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`](https://github.com/fballiano/openmage-cssjs-minify/blob/main/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:

```php
$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:

```php
$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:*

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

```

*After processing by the CssJsMinify observer:*

```html
<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`](https://github.com/fballiano/openmage-cssjs-minify/blob/main/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.

### Does this preservation logic apply to CSS link tags as well?

The module processes CSS files in a similar manner using a separate regex pattern (lines 68-95 of [`Observer.php`](https://github.com/fballiano/openmage-cssjs-minify/blob/main/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.