Skip to content

Commit 4509b63

Browse files
authored
release:minor new version
### New Features - **Increased free plan limit** to **2,000 monthly visitors** (previously 1,000) for more optimization power at no cost. - **Added offload metrics dashboard** with quick access shortcuts for tracking image optimization performance. - **Enhanced srcset detection and handling** for better responsive image optimization across all device sizes. ### Enhancements - **Improved lazy loading** to prioritize above-the-fold images and limit preloading to LCP (Largest Contentful Paint) elements only. - **Enhanced background image detection** with support for additional CSS selectors and properties. - **Added compatibility** with major caching plugins & hosting. - **Optimized image preloading logic** to detect and prioritize only the most critical images for faster page loads. - **Enhanced debug logging** capabilities for easier troubleshooting and issue diagnosis. - **Improved placeholder dimension calculations** for offloaded images to prevent layout shifts. - **Added accessibility improvements** with proper `aria-labels` and `rel` attributes on anchor tags. - **Removed intrusive customer satisfaction prompts** for a cleaner user experience. ### Bug Fixes - **Fixed incompatibility** with JetEngine Listing Grid “Load More” feature that prevented images from loading correctly. - **Fixed Jetpack newsletters** not displaying Optimole offloaded images, ensuring proper email rendering. - **Fixed image quality sampling issues** that prevented accurate preview of optimization settings. - **Fixed placeholder width and height calculations** when images are offloaded to the cloud. - **Resolved image storage selection issues** that could cause incorrect optimization paths.
2 parents ea8967d + bcf6460 commit 4509b63

File tree

114 files changed

+14013
-2186
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+14013
-2186
lines changed

.distignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ phpcs.xml
1717
phpunit.xml
1818
phpunit-cc.xml
1919
phpmd.xml
20+
jest.config.js
21+
jest.setup.js
2022
package.json
2123
package-lock.json
2224
composer.json
@@ -31,16 +33,19 @@ tests
3133
dist
3234
artifact
3335
assets/src
36+
assets/js/modules/__tests__
37+
coverage
3438
.wporg
3539
.nvmrc
3640
.github
3741
.eslintrc
3842
postcss.config.js
3943
tailwind.config.js
4044
phpstan.neon
45+
phpstan-baseline.neon
4146
development.php
4247
playwright.config.ts
4348
test-results
4449
wp-scripts.config.js
4550
README.md
46-
CHANGELOG.md
51+
CHANGELOG.md

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"ecmaVersion": 2021,
1212
"sourceType": "module"
1313
},
14-
"ignorePatterns": [ "node_modules", "assets/js" ],
14+
"ignorePatterns": [ "node_modules", "assets/js", "**/*.d.ts" ],
1515
"rules": {
1616
"camelcase" : "off",
1717
"indent": [

.github/workflows/deploy.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,24 @@ jobs:
2525
env:
2626
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
2727
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
28+
- name: Make dist
29+
run: npm run dist
30+
- name: Upload Latest Version to S3
31+
uses: jakejarvis/s3-sync-action@master
32+
with:
33+
args: --acl public-read --follow-symlinks --delete
34+
env:
35+
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET_DOWNLOADS }}
36+
AWS_ACCESS_KEY_ID: ${{ secrets.S3_DOWNLOADS_AWS_ACCESS_KEY_ID }}
37+
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_DOWNLOADS_AWS_SECRET_ACCESS_KEY }}
38+
AWS_REGION: "us-west-1" # optional: defaults to us-east-1
39+
SOURCE_DIR: "artifact" # optional: defaults to entire repository
40+
DEST_DIR: products/optimole-wp/latest
41+
- name: Clear Cloudfront Cache
42+
uses: chetan/invalidate-cloudfront-action@v2
43+
env:
44+
DISTRIBUTION: ${{ secrets.API_DIST_ID }}
45+
PATHS: "/download/optimole/*"
46+
AWS_REGION: "us-east-1"
47+
AWS_ACCESS_KEY_ID: ${{ secrets.S3_DOWNLOADS_AWS_ACCESS_KEY_ID }}
48+
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_DOWNLOADS_AWS_SECRET_ACCESS_KEY }}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: ESLint
1+
name: JavaScript Tests
22

33
on:
44
pull_request:
@@ -9,21 +9,36 @@ on:
99
concurrency:
1010
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
1111
cancel-in-progress: true
12+
1213
jobs:
13-
run:
14-
if: github.event.pull_request.draft == false
14+
test:
15+
if: github.event_name == 'push' || github.event.pull_request.draft == false
1516
runs-on: ubuntu-latest
16-
name: ESLint
17+
name: ESLint & Jest Tests
1718
steps:
18-
- uses: actions/checkout@master
19+
- uses: actions/checkout@v3
1920
with:
2021
persist-credentials: false
22+
2123
- name: Setup Node
22-
uses: actions/setup-node@v1
24+
uses: actions/setup-node@v3
2325
with:
2426
node-version: 18
25-
- name: Lint js files
27+
cache: 'npm'
28+
29+
- name: Install dependencies
2630
run: |
27-
npm install -g npm
2831
npm ci
32+
33+
- name: Run ESLint
34+
run: |
2935
npm run lint
36+
37+
- name: Run unit tests
38+
run: |
39+
npm test
40+
41+
- name: Run tests with coverage
42+
run: |
43+
npm run test:coverage
44+

.github/workflows/test-php.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,22 @@ jobs:
5656
name: PHPStan on PHP 8.0
5757
runs-on: ubuntu-latest
5858
steps:
59+
- name: Checkout source code
60+
uses: actions/checkout@v4
5961
- name: Setup PHP version
6062
uses: shivammathur/setup-php@v2
6163
with:
6264
php-version: '8.0'
6365
extensions: simplexml, mysql
64-
- name: Checkout source code
65-
uses: actions/checkout@v4
66+
- name: Setup Node
67+
uses: actions/setup-node@v4
68+
with:
69+
node-version: 18
70+
- name: Create the build file
71+
run: |
72+
npm ci
73+
npm run build
6674
- name: Install Composer dependencies
6775
run: composer install --prefer-dist --no-progress --no-suggest
6876
- name: PHPStan Static Analysis
69-
run: composer phpstan
77+
run: composer phpstan

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ build
1111
cc-test-reporter
1212
assets/build
1313
test-results
14-
tests/assets/filestash
14+
tests/assets/filestash
15+
coverage

assets/js/modal-attachment.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
jQuery(document).ready(function($) {
2+
if (!$('body').hasClass('upload-php')) {
3+
return;
4+
}
5+
6+
/**
7+
* Helper function to add Replace or Rename button to attachment actions
8+
* @param {Object} view - The attachment view instance
9+
*/
10+
function addReplaceRenameButton(view) {
11+
var $el = view.$el;
12+
var $actions = $el.find('.actions');
13+
14+
if (!$actions.length || $actions.find('.optml-replace-rename-link').length) {
15+
return;
16+
}
17+
18+
var attachmentId = view.model.get('id');
19+
20+
if (!attachmentId) {
21+
return;
22+
}
23+
24+
var editUrl = OptimoleModalAttachment.editPostURL + '?post=' + attachmentId +
25+
'&action=edit&TB_iframe=true&width=90%&height=90%';
26+
27+
var $editLink = $actions.find('a[href*="post.php"]');
28+
29+
if ($editLink.length) {
30+
$editLink.after(
31+
' <span class="links-separator">|</span>' +
32+
'<a href="' + editUrl + '" class="optml-replace-rename-link thickbox" title="' +
33+
OptimoleModalAttachment.i18n.replaceOrRename + '"> ' +
34+
OptimoleModalAttachment.i18n.replaceOrRename + '</a>'
35+
);
36+
}
37+
}
38+
39+
/**
40+
* Extend a WordPress media view with Replace/Rename functionality
41+
* @param {Object} OriginalView - The original view to extend
42+
* @returns {Object} Extended view
43+
*/
44+
function extendMediaView(OriginalView) {
45+
return OriginalView.extend({
46+
initialize: function() {
47+
OriginalView.prototype.initialize.apply(this, arguments);
48+
},
49+
50+
render: function() {
51+
OriginalView.prototype.render.apply(this, arguments);
52+
addReplaceRenameButton(this);
53+
return this;
54+
}
55+
});
56+
}
57+
58+
var originalAttachmentDetails = wp.media.view.Attachment.Details;
59+
wp.media.view.Attachment.Details = extendMediaView(originalAttachmentDetails);
60+
61+
if (wp.media.view.Attachment.Details.TwoColumn) {
62+
var originalTwoColumn = wp.media.view.Attachment.Details.TwoColumn;
63+
wp.media.view.Attachment.Details.TwoColumn = extendMediaView(originalTwoColumn);
64+
}
65+
66+
$(document).on('click', '.optml-replace-rename-link.thickbox', function() {
67+
tb_init('a.thickbox');
68+
});
69+
});

assets/js/modules/AGENTS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Optimole JavaScript Modules - Agent Instructions
2+
3+
4+
## Code standards
5+
- **CRITICAL**: All functions must have JSDoc comments with `@param` and `@return` annotations
6+
- Use ES6 modules with `import`/`export` syntax
7+
- Follow camelCase naming for functions and variables
8+
- Use `const`/`let` instead of `var`
9+
- Always handle errors gracefully with try/catch blocks
10+
- Use descriptive variable names and avoid abbreviations
11+
12+
## Testing instructions
13+
- Use only unit tests
14+

assets/js/modules/README.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Optimole JavaScript Modules
2+
3+
This directory contains the modular JavaScript components for the Optimole image optimizer.
4+
5+
## Module Structure
6+
7+
### Core Modules
8+
9+
#### `logger.js`
10+
- **Purpose**: Centralized logging functionality with debug mode support
11+
- **Exports**: `optmlLogger` object
12+
- **Key Features**:
13+
- Debug mode detection via URL params or localStorage
14+
- Structured logging with prefixes
15+
- Table logging support
16+
- Conditional logging based on debug state
17+
18+
#### `storage.js`
19+
- **Purpose**: Session storage management for tracking processed pages
20+
- **Exports**: `optmlStorage` object
21+
- **Key Features**:
22+
- Generate unique storage keys for URL/device combinations
23+
- Check if page/device combinations have been processed
24+
- Mark combinations as processed with timestamps
25+
- Error handling for storage operations
26+
27+
#### `device.js`
28+
- **Purpose**: Device type detection based on screen width
29+
- **Exports**: `optmlDevice` object
30+
- **Key Features**:
31+
- Mobile/desktop detection using 600px breakpoint
32+
- Device type constants (MOBILE: 1, DESKTOP: 2)
33+
- Helper methods for device type checking
34+
- PageSpeed Insights compatible detection logic
35+
36+
#### `api.js`
37+
- **Purpose**: REST API communication with fallback mechanisms
38+
- **Exports**: `optmlApi` object
39+
- **Key Features**:
40+
- Primary sendBeacon API for reliable data transmission
41+
- Fetch API fallback for when sendBeacon fails
42+
- Automatic storage marking on successful sends
43+
- Error handling and logging
44+
45+
#### `dom-utils.js`
46+
- **Purpose**: DOM manipulation and utility functions
47+
- **Exports**: `optmlDomUtils` object
48+
- **Key Features**:
49+
- Debounce utility for performance optimization
50+
- Unique CSS selector generation for elements
51+
- Background image detection and URL extraction
52+
- Page condition checking (viewport, visibility, load state)
53+
- Promise-based waiting utilities
54+
55+
### Specialized Modules
56+
57+
#### `background.js`
58+
- **Purpose**: Background image handling and lazy loading observation
59+
- **Exports**: `optmlBackground` object
60+
- **Key Features**:
61+
- Background image lazy loading detection
62+
- Mutation observer setup for class changes
63+
- URL extraction from background images
64+
- Selector processing and element observation
65+
66+
#### `lcp.js`
67+
- **Purpose**: Largest Contentful Paint (LCP) element detection
68+
- **Exports**: `optmlLcp` object
69+
- **Key Features**:
70+
- Performance Observer integration
71+
- LCP element identification and processing
72+
- Support for both image and background image LCP elements
73+
- Timeout handling for LCP detection
74+
75+
#### `image-detector.js`
76+
- **Purpose**: Image detection and dimension analysis
77+
- **Exports**: `optmlImageDetector` object
78+
- **Key Features**:
79+
- Missing dimension detection for images
80+
- Intersection Observer setup for above-fold detection
81+
- Optimole image observation (`data-opt-id` attributes)
82+
- Cleanup utilities for temporary attributes
83+
84+
#### `srcset-detector.js`
85+
- **Purpose**: Srcset analysis and missing variation detection for eligible images
86+
- **Exports**: `optmlSrcsetDetector` object
87+
- **Key Features**:
88+
- Smart image detection with improved skip logic:
89+
- Includes images without `data-opt-src` (non-lazyload images)
90+
- Includes images with `data-opt-src` AND `data-opt-lazy-loaded` (completed lazyload)
91+
- Skips images with only `data-opt-src` (pending lazyload)
92+
- **Aspect ratio detection for cropping requirements**:
93+
- Compares natural vs. displayed aspect ratios to determine if cropping is needed
94+
- Uses intelligent thresholds: 5% tolerance, 15% significant difference, 10% ratio change
95+
- Prevents unnecessary cropping when aspect ratios match
96+
- Returns separate crop status data as `imageId: cropStatus` mapping
97+
- Calculates required srcset variations using comprehensive size ranges:
98+
- Mobile range: 200w-500w (50w steps) for dense mobile coverage
99+
- Tablet range: 500w-800w (100w steps) for tablet devices
100+
- Desktop range: 800w-1200w (200w steps) for desktop screens
101+
- High-res range: 1200w-1600w (200w steps) with strategic 2x DPR
102+
- Configurable generation settings:
103+
- `widthStepSize`: Step size for width variations (default: 100px)
104+
- `minSize`: Minimum image size to consider (default: 200px)
105+
- `maxVariations`: Maximum srcset variations per image (default: 8)
106+
- `sizeTolerance`: Tolerance for existing sizes (default: 50px)
107+
- Analyzes existing srcset attributes to identify missing sizes
108+
- **Ultra-compact API payload**: Sends only essential fields with short names (w, h, d, s, b)
109+
- **Separate crop status**: Returns crop requirements as `imageId: cropStatus` mapping instead of per-srcset flags
110+
- **Full logging**: Complete analysis data available in console logs for debugging
111+
- Smart selection from dense size grid for optimal responsive coverage
112+
113+
#### `main.js`
114+
- **Purpose**: Main orchestrator coordinating all functionality
115+
- **Exports**: `optmlMain` object
116+
- **Key Features**:
117+
- Complete above-the-fold detection workflow
118+
- Module coordination and data aggregation
119+
- API data preparation and submission with separate crop status
120+
- Error handling and condition checking
121+
122+
## Data Structure
123+
124+
### API Payload Structure
125+
The main module sends data to the REST API with the following structure:
126+
127+
```javascript
128+
{
129+
d: deviceType, // Device type (1=mobile, 2=desktop)
130+
a: aboveTheFoldImages, // Array of above-fold image IDs
131+
b: backgroundSelectors, // Background image selectors
132+
u: url, // Page URL
133+
t: timestamp, // Request timestamp
134+
h: hmac, // Security hash
135+
l: lcpData, // LCP (Largest Contentful Paint) data
136+
m: missingDimensions, // Missing dimension data
137+
s: srcsetData, // Srcset variations data
138+
c: cropStatusData // Crop status mapping (imageId -> boolean)
139+
}
140+
```
141+
142+
### Srcset Data Structure
143+
Individual srcset entries contain:
144+
```javascript
145+
{
146+
w: width, // Image width
147+
h: height, // Image height
148+
d: dpr, // Device pixel ratio
149+
s: descriptor, // Srcset descriptor (e.g., "300w")
150+
b: breakpoint // CSS breakpoint
151+
}
152+
```
153+
154+
### Crop Status Data Structure
155+
Crop requirements are stored separately:
156+
```javascript
157+
{
158+
882936320: true, // Image ID -> requires cropping
159+
123456789: false // Image ID -> no cropping needed
160+
}
161+
```
162+

0 commit comments

Comments
 (0)