Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CI

on:
push:
branches: [master, main, beta]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- php: '8.2'
- php: '8.3'
- php: '8.4'
- php: '8.5'
defaults:
run:
working-directory: core/components/minifyx
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: gd, mbstring, json
coverage: none
- name: Validate PHP syntax
run: find src model -name '*.php' -print0 | xargs -0 -n1 php -l
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: PHPUnit
run: composer test
- name: PHPStan
run: composer phpstan
- name: Composer audit
run: composer audit

imagick:
runs-on: ubuntu-latest
defaults:
run:
working-directory: core/components/minifyx
steps:
- uses: actions/checkout@v4
- name: Setup PHP with Imagick
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: imagick, mbstring, json
coverage: none
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Test Imagick processor
run: composer test -- --filter ImagickImageProcessorTest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.idea
core/components/minifyx/munee/*
/core/components/minifyx/vendor/
/core/components/minifyx/.phpunit.cache/
/core/components/minifyx/.phpunit.result.cache
/core/components/minifyx/.phpstan.cache/
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

105 changes: 104 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,104 @@
MinifyX is a MODX® Revolution addon that allows to combine and minify JS and CSS files to speed up your site and reduce server load.
# MinifyX

MODX Revolution extra: combine and minify CSS/JS, compile SCSS/LESS, resize images on request, optionally minify HTML.

Version **3.0** targets MODX 3 and PHP 8.2, upgrades scssphp to 2.x, and removes runtime CoffeeScript compilation.

## Requirements

- MODX Revolution **3.x**
- PHP **8.2+**
- `vendor/` from Composer (included in the transport package)
- GD or Imagick if you enable image processing

## Usage

| Entry | Role |
|---|---|
| Snippet `MinifyX` | Explicit CSS/JS groups and sources |
| `minify()` | Same API from PHP after `OnMODXInit` |
| Plugin | Opt-in: registered assets, images, HTML minify |
| `/assets/components/minifyx/minifyx.php` | Image connector (GD/Imagick backend) |

Enable plugin modes with system settings: `minifyx_process_registered`, `minifyx_process_images`, `minifyx_minifyHtml`.

### Asset groups

Copy `core/components/minifyx/config/groups.php.example` to `groups.php`. Groups are flat named lists:

```php
return [
'baseCss' => ['/assets/css/base.css'],
'baseJs' => ['/assets/js/base.js'],
];
```

Set `cssGroups` to `baseCss` and `jsGroups` to `baseJs` in the snippet call.

### Optional production hardening

- `minifyx_bundleIntegrity`: calculate SHA-384 SRI for generated bundles.
- `minifyx_cors_origin`: emit `Access-Control-Allow-Origin` from the image connector.
- `minifyx_image_signing_key`: require signed image transformation URLs.
- `minifyx_image_signing_keys`: rotate signing keys; the first signs and all listed keys verify.
- `minifyx_image_rate_limit_max` / `minifyx_image_rate_limit_window`: optional file-based connector rate limit.
- `minifyx_debug`: safe bundle comments with source count, cache status and bundle filename.
- `minifyx_jsManglerMaxInputBytes`: cap external optimizer input before process launch.
- `minifyx_preloadCss` / `minifyx_preloadJs`: emit preload hints for bundles.
- `minifyx_mangleJs`: use Terser or esbuild when available.
- `minifyx_bundleJsModules`: bundle registered `type="module"` scripts with esbuild. If esbuild fails or is
unavailable, MinifyX keeps the original module tags.
- `minifyx_esbuildPath`: optional dedicated esbuild binary path for module bundling.
- `minifyx_sourceMaps`: write external `.map` files for Terser and esbuild output.
- `minifyx_parallelBuild`: allow bounded parallel group builds in the warm-cache CLI.

Run `php core/components/minifyx/bin/health-check.php --base-path=/path/to/modx` to inspect cache,
image drivers, external JS tools and signing configuration. The command is CLI-only.

SCSS and LESS imports remain under their compilers' control. MinifyX tracks local transitive
`@import`/`@use`/`@forward` files inside the webroot so edits invalidate the bundle cache. Source maps cover Terser,
esbuild, and a single unminified SCSS/Sass input. Minified or multi-input SCSS and LESS output remains map-free.

Warm all groups, or select one group:

```bash
php core/components/minifyx/bin/warm-cache.php --base-path=/path/to/modx
php core/components/minifyx/bin/warm-cache.php --base-path=/path/to/modx --group=baseCss
```

Add `--parallel --jobs=2` to build independent groups in bounded child processes when `proc_open` is available.
Without it, the command keeps deterministic group ordering and builds sequentially.

## Stack

- CSS/JS: [matthiasmullie/minify](https://github.com/matthiasmullie/minify)
- SCSS: [scssphp/scssphp](https://github.com/scssphp/scssphp)
- LESS: [wikimedia/less.php](https://github.com/wikimedia/less.php)
- Images: native GD / optional Imagick via `ImageProcessorFactory`

Precompile CoffeeScript to `.js`. MinifyX returns `unsupported_source_type` for `.coffee` input.

## Development

```bash
cd core/components/minifyx
composer install
composer test
composer phpstan
```

Build the transport package only after a production Composer install:

```bash
cd core/components/minifyx
composer install --no-dev --optimize-autoloader
```

Then run `_build/build.transport.php` against a MODX install. Package version lives in `_build/build.config.php`.

## Docs

- [Changelog](core/components/minifyx/docs/changelog.txt)
- [Migration 1.x → 2.0](core/components/minifyx/docs/migration-2.0.md)
- [Migration 2.x → 3.0](core/components/minifyx/docs/migration-3.0.md)
- [3.x release checklist](core/components/minifyx/docs/release-3.x-checklist.md)
18 changes: 4 additions & 14 deletions _build/build.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
/* define package */
define('PKG_NAME', 'MinifyX');
define('PKG_NAME_LOWER', strtolower(PKG_NAME));
define('PKG_VERSION', '1.7.1');
define('PKG_VERSION', '3.0.0');
define('PKG_RELEASE', 'pl');
define('PKG_AUTO_INSTALL', false);
define('PKG_PREPARE', false);
define('PKG_PREPARE', true);

/* define paths */
if (isset($_SERVER['MODX_BASE_PATH'])) {
Expand All @@ -29,23 +29,13 @@
define('MODX_ASSETS_URL', MODX_BASE_URL . 'assets/');

/* define build options */
//define('BUILD_MENU_UPDATE', false);
//define('BUILD_ACTION_UPDATE', false);
define('BUILD_SETTING_UPDATE', false);
//define('BUILD_CHUNK_UPDATE', false);

define('BUILD_SETTING_UPDATE', true);
define('BUILD_SNIPPET_UPDATE', true);
define('BUILD_PLUGIN_UPDATE', true);
//define('BUILD_EVENT_UPDATE', true);
//define('BUILD_POLICY_UPDATE', true);
//define('BUILD_POLICY_TEMPLATE_UPDATE', true);
//define('BUILD_PERMISSION_UPDATE', true);

//define('BUILD_CHUNK_STATIC', false);
define('BUILD_SNIPPET_STATIC', false);
define('BUILD_PLUGIN_STATIC', false);

$BUILD_RESOLVERS = [
'files',
'setup',
];
];
42 changes: 23 additions & 19 deletions _build/build.prepare.php
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
<?php

$root = dirname(dirname(__FILE__)) . '/';
require_once $root . '_build/includes/functions.php';
$base = $root . 'core/components/minifyx/munee/';
$root = dirname(__DIR__) . '/';
$component = $root . 'core/components/minifyx/';

// Clean base dir
if ($dirs = @scandir($base)) {
foreach ($dirs as $dir) {
if (!in_array($dir, array('src', 'config', 'vendor', '.', '..'))) {
$path = $base . $dir;
if (is_dir($path)) {
removeDir($path);
}
else {
unlink($path);
}
}
}
// Remove legacy Munee submodule leftovers from the package tree.
$legacyMunee = $component . 'munee/';
if (is_dir($legacyMunee)) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($legacyMunee, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $file) {
$file->isDir() ? @rmdir($file->getPathname()) : @unlink($file->getPathname());
}
@rmdir($legacyMunee);
}

// Clean vendors
$base = $root . 'core/components/minifyx/vendor/';
cleanPackages($base);
$vendorAutoload = $component . 'vendor/autoload.php';
if (!is_file($vendorAutoload)) {
fwrite(
STDERR,
"MinifyX prepare: vendor/autoload.php is missing.\n"
. "Run: composer install --no-dev --optimize-autoloader\n"
. "inside core/components/minifyx/ before building the transport package.\n"
);
exit(1);
}
108 changes: 108 additions & 0 deletions _build/data/transport.settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
'xtype' => 'combo-boolean',
'value' => false,
],
'debug' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'process_images' => [
'xtype' => 'combo-boolean',
'value' => false,
Expand Down Expand Up @@ -54,6 +58,10 @@
'xtype' => 'textfield',
'value' => '/assets/components/minifyx/cache/',
],
'cache' => [
'xtype' => 'textfield',
'value' => MODX_CORE_PATH . 'cache/default/minifyx/',
],
'forceUpdate' => [
'xtype' => 'combo-boolean',
'value' => false,
Expand All @@ -66,6 +74,106 @@
'xtype' => 'combo-boolean',
'value' => false,
],
'connector' => [
'xtype' => 'textfield',
'value' => '/assets/components/minifyx/minifyx.php',
],
'imageProcessor' => [
'xtype' => 'list',
'value' => 'GD',
'options' => [
['text' => 'GD', 'value' => 'GD'],
['text' => 'Imagick', 'value' => 'Imagick'],
],
],
'image_signing_key' => [
'xtype' => 'textfield',
'value' => '',
],
'image_signing_keys' => [
'xtype' => 'textarea',
'value' => '',
],
'image_max_pixels' => [
'xtype' => 'numberfield',
'value' => 20000000,
],
'image_max_bytes' => [
'xtype' => 'numberfield',
'value' => 20000000,
],
'image_rate_limit_max' => [
'xtype' => 'numberfield',
'value' => 0,
],
'image_rate_limit_window' => [
'xtype' => 'numberfield',
'value' => 60,
],
'image_rate_limit_salt' => [
'xtype' => 'textfield',
'value' => '',
],
'mangleJs' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'bundleJsModules' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'sourceMaps' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'parallelBuild' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'jsMangler' => [
'xtype' => 'list',
'value' => 'terser',
'options' => [
['text' => 'Terser', 'value' => 'terser'],
['text' => 'esbuild', 'value' => 'esbuild'],
],
],
'jsManglerPath' => [
'xtype' => 'textfield',
'value' => '',
],
'esbuildPath' => [
'xtype' => 'textfield',
'value' => '',
],
'jsManglerMaxInputBytes' => [
'xtype' => 'numberfield',
'value' => 5000000,
],
'preloadCss' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'preloadJs' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'bundleIntegrity' => [
'xtype' => 'combo-boolean',
'value' => false,
],
'cors_origin' => [
'xtype' => 'textfield',
'value' => '',
],
'cssPreloadTpl' => [
'xtype' => 'textfield',
'value' => '',
],
'jsPreloadTpl' => [
'xtype' => 'textfield',
'value' => '',
],
];

foreach ($tmp as $k => $v) {
Expand Down
Loading
Loading