@benpley/wappler-smart-image
v1.0.2
Published
On-the-fly image processing for Wappler with Sharp - resize, convert formats, and intelligent caching
Maintainers
Readme
Smart Image for Wappler
A full-stack image optimization extension for Wappler that automatically resizes, converts, and caches images on the fly — served through a clean <smart-img> component that is a direct drop-in replacement for the standard HTML <img> element.
Authors: Shalabh Gupta & Ben Pleysier
⚠️ Node.js only — This extension requires a Wappler Node.js project. It is not compatible with PHP or ASP.NET Wappler projects. The server-side processing depends on Node.js-specific libraries (Sharp, Express route hooks, and EJS templates).
Why Smart Image?
Replacing <img> with <smart-img> requires no configuration and gives you:
- ✅ Automatic responsive images — the right size for mobile, tablet, and desktop
- ✅ Retina / HiDPI support — 2× variants served automatically to high-density screens
- ✅ Modern formats — WebP or AVIF generated on the fly from your original JPG/PNG
- ✅ Blur-up placeholder — a blurred preview is shown instantly while the full image loads
- ✅ Lazy loading — built in by default
- ✅ Server-side caching — processed images are cached and served in subsequent requests at zero cost
- ✅ Zero layout shift — set
widthandheightto reserve space before load
Installation
Install via Wappler's built-in extension manager:
- Open your project in Wappler
- Go to Project Options → Extensions
- Search for and install Smart Image
On install, the extension automatically sets up:
app/config/smart-image.json— project configuration file- The server-side route hook so the
image()EJS helper is available - The
<smart-img>runtime under/public/js/smart-image.js - The App Connect component tile for use in the visual editor
Quick Start
Drop-in replacement for <img>
Simply swap <img> for <smart-img>:
<!-- Before -->
<img src="/assets/images/hero.jpg" alt="Hero">
<!-- After — responsive, retina-aware, lazy-loaded, with blur placeholder -->
<smart-img src="/assets/images/hero.jpg" alt="Hero"></smart-img>That single change delivers desktop, tablet, and mobile variants in AVIF or WebP format, with a retina 2× version for each — all generated and cached automatically.
In the Wappler Visual Editor
Click on the Smart Image tile (found under Media) onto your page. Set the src to your source image and adjust widths and format as needed.
With explicit sizing
<smart-img
id="hero_image"
src="/assets/images/hero.jpg"
alt="Hero banner"
desktop-width="1600"
tablet-width="1024"
mobile-width="640"
format="avif"
width="1600"
height="900">
</smart-img>How It Works
Server Side (modules/smart-image.js)
When an image is requested, the server-side module:
- Validates the source path (must be in a configured source folder, no special characters)
- Checks the cache — if a fresh cached variant exists, returns its versioned URL immediately
- Auto-downscales oversized images if they exceed
maxWidth/maxHeight - Processes the image using Sharp — resize, convert, apply quality settings
- Writes the result to
/public/<cacheRoot>/...and serves it withETagandLast-Modifiedheaders for automatic browser cache invalidation - Optionally prepends a CDN base URL to the returned path
A concurrency lock ensures the same file is never generated twice simultaneously.
Cache Invalidation
No ?v= version strings are needed. Cache invalidation is handled automatically through the following chain:
- Every request to a cache URL (e.g.
/img-cache/assets/images/hero-1600.webp) passes through the route handler - The handler compares the source image
mtimeagainst the cached filemtime - If the source is newer (e.g. you replaced
hero.jpgwith a different image of the same name), the cache file is regenerated automatically - The regenerated file has a new
mtime, which means a newETag - The browser's previously cached
ETagno longer matches → Express returns200with the fresh content - On subsequent requests where nothing has changed → Express returns
304 Not Modified— instant, zero bandwidth
This means replacing a source image on disk — even with a completely different image of the same filename and dimensions — is detected and handled correctly without any manual cache clearing or version management.
Client Side (includes/smart-image.js)
The <smart-img> custom element:
- Detects the current breakpoint (mobile/tablet/desktop) and pixel density using media queries and
devicePixelRatio - Constructs the correct cache URL matching the server-side naming convention
- Displays a blur-up placeholder (auto-generated from a tiny canvas render, or a manual image)
- Loads the optimised image and fades it in once ready
- Falls back to the original source image if the optimised variant fails to load
- Reacts to viewport changes — swaps to the appropriate variant when the viewport is resized
<smart-img> Attributes
Images
| Attribute | Default | Description |
|---|---|---|
| src | — | Source image path (must be under a configured source folder) |
| desktop-width | 1600 | Target width for desktop |
| tablet-width | 1024 | Target width for tablet |
| mobile-width | 640 | Target width for mobile |
| retina | 2 | Retina multiplier applied to each breakpoint width |
| format | webp | Output format: avif, webp, jpeg, png |
| fallback-format | jpeg | Fallback format if the primary image fails to load |
| cache-root | img-cache | Cache folder prefix — change only if configured differently on the server |
Breakpoints
| Attribute | Default | Description |
|---|---|---|
| mobile-breakpoint | 767 | Max viewport width (px) at which the mobile image is used |
| tablet-breakpoint | 1199 | Max viewport width (px) at which the tablet image is used |
Options
| Attribute | Default | Description |
|---|---|---|
| alt | — | Alt text for screen readers (supports App Connect data binding) |
| title | — | Tooltip title attribute |
| class | — | CSS classes applied to the inner <img> (e.g. img-fluid rounded) |
| loading | lazy | lazy or eager |
| fetchpriority | auto | auto, high, or low — use high for above-the-fold images |
| placeholder-mode | auto | auto (blur), manual (use placeholder attribute), or none |
| placeholder | — | Manual placeholder image URL shown while the main image loads |
| width | — | Intrinsic width in pixels — prevents layout shift (CLS) |
| height | — | Intrinsic height in pixels — prevents layout shift (CLS) |
Events
| Event | Description |
|---|---|
| load | Fired when the image has successfully loaded |
| error | Fired when the image fails to load |
Project Configuration
Configuration lives at app/config/smart-image.json:
{
"cacheRoot": "img-cache",
"cdnBase": "",
"sourceFolders": [
"assets/logo",
"assets/images",
"assets/blog"
],
"warmOnStart": false,
"warmVariants": [
{ "width": 1200, "format": "webp" },
{ "width": 1600, "format": "webp" },
{ "width": 1600, "format": "jpeg" }
],
"maxWidth": 2400,
"maxHeight": 2400,
"defaultQuality": 75,
"allowedSourceFormats": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
}Troubleshooting
Images not processing / image is not defined in EJS
- The startup route hook was not registered, or the server has not been restarted since install.
- Verify that
extensions/server_connect/routes/smart-image.jsexists.
Image not found warnings in the console
- The source image path must be inside one of the configured
sourceFolders. - File names must use only letters, numbers, dashes, underscores, and dots — no spaces or special characters.
Version
Current version: 1.0.2 — Fix Node route hook package require path
What's included in 1.0.2
- Fixed the generated Node route hook so
extensions/server_connect/routes/smart-image.jsnow requires@benpley/wappler-smart-image/routes/smart-image-routes - Kept the legacy alias file
extensions/server_connect/smart-image.jspointing to./routes/smart-image
Previously included in 1.0.1
Server-side (Node.js / Sharp)
- On-the-fly image resizing and format conversion (AVIF, WebP, JPEG, PNG) via Sharp
- Automatic cache invalidation by comparing source and cache file
mtime— no manual version strings needed - Concurrency lock prevents duplicate generation when multiple requests arrive for the same variant simultaneously
- Auto-downscale of oversized source images that exceed the configured
maxWidth/maxHeight - Optional cache warm-up on server start (
warmOnStart) to pre-generate variants for all configured source folders - Orphan cache cleanup utility — removes cached variants whose source images no longer exist
Client-side (<smart-img> custom element)
- Breakpoint-aware image selection (desktop / tablet / mobile) with configurable breakpoint thresholds
- Retina / HiDPI support — 2× (or custom multiplier) variants served automatically
- Blur-up placeholder with three modes:
auto(generated),manual(custom image),none - Optional fallback image when the optimised variant fails to load
display-sizeattribute:responsive(fills available width) ororiginal(natural size)- Reacts to viewport resize — swaps to the correct variant dynamically
Wappler integration
- Visual editor tile under Media with full property panel and App Connect data-binding support for
alt app/config/smart-image.jsonscaffolded automatically on install- Express route hook registered at startup — no manual wiring required
License
MIT — developed for the Wappler community.
