{"_id":"@benpley/wappler-smart-image","_rev":"5-af0162dc3214e4925017487867e24f75","name":"@benpley/wappler-smart-image","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.1":{"name":"@benpley/wappler-smart-image","version":"1.0.1","keywords":["wappler","wappler-extension","wappler-module","server-connect","image","smart-image","sharp","resize","webp","avif","optimization","cache","responsive-images"],"author":{"name":"Shalabh Gupta and Ben Pleysier"},"license":"MIT","_id":"@benpley/wappler-smart-image@1.0.1","maintainers":[{"name":"benpley","email":"ben@pleysier.com.au"}],"dist":{"shasum":"139aac66d0d08e76601ff87e5e0a01e2d2b85c60","tarball":"https://registry.npmjs.org/@benpley/wappler-smart-image/-/wappler-smart-image-1.0.1.tgz","fileCount":10,"integrity":"sha512-OukK+jhgNAAcWfKoskmCMVTd8wWAKFtbXGZmH30BaHdRm4sWPnaRyNZRj5UBaVJVLbnikApDyvzpv03FXUGAeQ==","signatures":[{"sig":"MEUCIGoYuzpCP0xxv+xqebhBVL5vqsM0Cz9X1eantHS2jEsnAiEA8iQWCTGNCctJ/RGqbmOMBuvQIQ1UYuNKRYs+VzQU23o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":59354},"main":"index.js","engines":{"node":">=18.17.0"},"scripts":{"postinstall":"node postinstall.js"},"wappler":{"path":"modules/smart-image","type":"module","module":"smart-image"},"_npmUser":{"name":"benpley","email":"ben@pleysier.com.au"},"_npmVersion":"11.12.1","description":"On-the-fly image processing for Wappler with Sharp - resize, convert formats, and intelligent caching","directories":{},"_nodeVersion":"22.19.0","_hasShrinkwrap":false,"peerDependencies":{"sharp":"^0.33.5","express":"^4.17.0"},"_npmOperationalInternal":{"tmp":"tmp/wappler-smart-image_1.0.1_1778737440029_0.9576653034236828","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@benpley/wappler-smart-image","version":"1.0.2","description":"On-the-fly image processing for Wappler with Sharp - resize, convert formats, and intelligent caching","main":"index.js","keywords":["wappler","wappler-extension","wappler-module","server-connect","image","smart-image","sharp","resize","webp","avif","optimization","cache","responsive-images"],"author":{"name":"Shalabh Gupta and Ben Pleysier"},"license":"MIT","engines":{"node":">=18.17.0"},"peerDependencies":{"express":"^4.17.0","sharp":"^0.33.5"},"wappler":{"type":"module","module":"smart-image","path":"modules/smart-image"},"scripts":{"postinstall":"node postinstall.js"},"_id":"@benpley/wappler-smart-image@1.0.2","_nodeVersion":"22.19.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-mhh40w1UI9jXX3ZWXfrX3243ktRekPx7uRGFujI/0splMYaXRcbQMJUadbsJeR+glPBrbYo7Ea29J+cIVrC/1A==","shasum":"dc5320441402ff061667559348b6ed32f6e86870","tarball":"https://registry.npmjs.org/@benpley/wappler-smart-image/-/wappler-smart-image-1.0.2.tgz","fileCount":10,"unpackedSize":59671,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDa04D3nvrdvzI0iUtvxEOlZyV7DScXSKcA9azdXNs3AwIgYK0MzrOta9W6opEn/G3exey7WyOXqYkK7UGq82Pl6Uc="}]},"_npmUser":{"name":"benpley","email":"ben@pleysier.com.au"},"directories":{},"maintainers":[{"name":"benpley","email":"ben@pleysier.com.au"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/wappler-smart-image_1.0.2_1778765614697_0.4068538067494196"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-14T05:43:59.916Z","modified":"2026-05-14T13:33:34.953Z","1.0.0":"2026-05-14T05:16:57.776Z","1.0.1":"2026-05-14T05:44:00.192Z","1.0.2":"2026-05-14T13:33:34.837Z"},"author":{"name":"Shalabh Gupta and Ben Pleysier"},"license":"MIT","keywords":["wappler","wappler-extension","wappler-module","server-connect","image","smart-image","sharp","resize","webp","avif","optimization","cache","responsive-images"],"description":"On-the-fly image processing for Wappler with Sharp - resize, convert formats, and intelligent caching","maintainers":[{"name":"benpley","email":"ben@pleysier.com.au"}],"readme":"# Smart Image for Wappler\n\nA full-stack image optimization extension for [Wappler](https://wappler.io) 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.\n\n**Authors:** Shalabh Gupta & Ben Pleysier\n\n> ⚠️ **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).\n\n---\n\n## Why Smart Image?\n\nReplacing `<img>` with `<smart-img>` requires no configuration and gives you:\n\n- ✅ **Automatic responsive images** — the right size for mobile, tablet, and desktop\n- ✅ **Retina / HiDPI support** — 2× variants served automatically to high-density screens\n- ✅ **Modern formats** — WebP or AVIF generated on the fly from your original JPG/PNG\n- ✅ **Blur-up placeholder** — a blurred preview is shown instantly while the full image loads\n- ✅ **Lazy loading** — built in by default\n- ✅ **Server-side caching** — processed images are cached and served in subsequent requests at zero cost\n- ✅ **Zero layout shift** — set `width` and `height` to reserve space before load\n\n---\n\n## Installation\n\nInstall via **Wappler's built-in extension manager**:\n\n1. Open your project in Wappler\n2. Go to **Project Options → Extensions**\n3. Search for and install **Smart Image**\n\nOn install, the extension automatically sets up:\n- `app/config/smart-image.json` — project configuration file\n- The server-side route hook so the `image()` EJS helper is available\n- The `<smart-img>` runtime under `/public/js/smart-image.js`\n- The App Connect component tile for use in the visual editor\n\n---\n\n## Quick Start\n\n### Drop-in replacement for `<img>`\n\nSimply swap `<img>` for `<smart-img>`:\n\n```html\n<!-- Before -->\n<img src=\"/assets/images/hero.jpg\" alt=\"Hero\">\n\n<!-- After — responsive, retina-aware, lazy-loaded, with blur placeholder -->\n<smart-img src=\"/assets/images/hero.jpg\" alt=\"Hero\"></smart-img>\n```\n\nThat 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.\n\n### In the Wappler Visual Editor\n\nClick 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.\n\n### With explicit sizing\n\n```html\n<smart-img\n  id=\"hero_image\"\n  src=\"/assets/images/hero.jpg\"\n  alt=\"Hero banner\"\n  desktop-width=\"1600\"\n  tablet-width=\"1024\"\n  mobile-width=\"640\"\n  format=\"avif\"\n  width=\"1600\"\n  height=\"900\">\n</smart-img>\n```\n\n\n## How It Works\n\n### Server Side (`modules/smart-image.js`)\n\nWhen an image is requested, the server-side module:\n\n1. **Validates** the source path (must be in a configured source folder, no special characters)\n2. **Checks the cache** — if a fresh cached variant exists, returns its versioned URL immediately\n3. **Auto-downscales** oversized images if they exceed `maxWidth` / `maxHeight`\n4. **Processes the image** using Sharp — resize, convert, apply quality settings\n5. **Writes the result** to `/public/<cacheRoot>/...` and serves it with `ETag` and `Last-Modified` headers for automatic browser cache invalidation\n6. **Optionally prepends a CDN base URL** to the returned path\n\nA **concurrency lock** ensures the same file is never generated twice simultaneously.\n\n### Cache Invalidation\n\nNo `?v=` version strings are needed. Cache invalidation is handled automatically through the following chain:\n\n1. Every request to a cache URL (e.g. `/img-cache/assets/images/hero-1600.webp`) passes through the route handler\n2. The handler compares the **source image `mtime`** against the **cached file `mtime`**\n3. If the source is newer (e.g. you replaced `hero.jpg` with a different image of the same name), the cache file is **regenerated automatically**\n4. The regenerated file has a new `mtime`, which means a new `ETag`\n5. The browser's previously cached `ETag` no longer matches → Express returns `200` with the fresh content\n6. On subsequent requests where nothing has changed → Express returns `304 Not Modified` — instant, zero bandwidth\n\nThis 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.\n\n### Client Side (`includes/smart-image.js`)\n\nThe `<smart-img>` custom element:\n\n1. **Detects the current breakpoint** (mobile/tablet/desktop) and pixel density using media queries and `devicePixelRatio`\n2. **Constructs the correct cache URL** matching the server-side naming convention\n3. **Displays a blur-up placeholder** (auto-generated from a tiny canvas render, or a manual image)\n4. **Loads the optimised image** and fades it in once ready\n5. **Falls back** to the original source image if the optimised variant fails to load\n6. **Reacts to viewport changes** — swaps to the appropriate variant when the viewport is resized\n\n---\n\n## `<smart-img>` Attributes\n\n### Images\n\n| Attribute | Default | Description |\n|---|---|---|\n| `src` | — | Source image path (must be under a configured source folder) |\n| `desktop-width` | `1600` | Target width for desktop |\n| `tablet-width` | `1024` | Target width for tablet |\n| `mobile-width` | `640` | Target width for mobile |\n| `retina` | `2` | Retina multiplier applied to each breakpoint width |\n| `format` | `webp` | Output format: `avif`, `webp`, `jpeg`, `png` |\n| `fallback-format` | `jpeg` | Fallback format if the primary image fails to load |\n| `cache-root` | `img-cache` | Cache folder prefix — change only if configured differently on the server |\n\n### Breakpoints\n\n| Attribute | Default | Description |\n|---|---|---|\n| `mobile-breakpoint` | `767` | Max viewport width (px) at which the mobile image is used |\n| `tablet-breakpoint` | `1199` | Max viewport width (px) at which the tablet image is used |\n\n### Options\n\n| Attribute | Default | Description |\n|---|---|---|\n| `alt` | — | Alt text for screen readers (supports App Connect data binding) |\n| `title` | — | Tooltip title attribute |\n| `class` | — | CSS classes applied to the inner `<img>` (e.g. `img-fluid rounded`) |\n| `loading` | `lazy` | `lazy` or `eager` |\n| `fetchpriority` | `auto` | `auto`, `high`, or `low` — use `high` for above-the-fold images |\n| `placeholder-mode` | `auto` | `auto` (blur), `manual` (use `placeholder` attribute), or `none` |\n| `placeholder` | — | Manual placeholder image URL shown while the main image loads |\n| `width` | — | Intrinsic width in pixels — prevents layout shift (CLS) |\n| `height` | — | Intrinsic height in pixels — prevents layout shift (CLS) |\n\n### Events\n\n| Event | Description |\n|---|---|\n| `load` | Fired when the image has successfully loaded |\n| `error` | Fired when the image fails to load |\n\n---\n\n## Project Configuration\n\nConfiguration lives at `app/config/smart-image.json`:\n\n```json\n{\n  \"cacheRoot\": \"img-cache\",\n  \"cdnBase\": \"\",\n  \"sourceFolders\": [\n    \"assets/logo\",\n    \"assets/images\",\n    \"assets/blog\"\n  ],\n  \"warmOnStart\": false,\n  \"warmVariants\": [\n    { \"width\": 1200, \"format\": \"webp\" },\n    { \"width\": 1600, \"format\": \"webp\" },\n    { \"width\": 1600, \"format\": \"jpeg\" }\n  ],\n  \"maxWidth\": 2400,\n  \"maxHeight\": 2400,\n  \"defaultQuality\": 75,\n  \"allowedSourceFormats\": [\".jpg\", \".jpeg\", \".png\", \".webp\", \".svg\"]\n}\n```\n\n---\n\n## Troubleshooting\n\n**Images not processing / `image is not defined` in EJS**\n- The startup route hook was not registered, or the server has not been restarted since install.\n- Verify that `extensions/server_connect/routes/smart-image.js` exists.\n\n**Image not found warnings in the console**\n- The source image path must be inside one of the configured `sourceFolders`.\n- File names must use only letters, numbers, dashes, underscores, and dots — no spaces or special characters.\n\n---\n\n## Version\n\n**Current version:** 1.0.2 — Fix Node route hook package require path\n\n### What's included in 1.0.2\n\n- Fixed the generated Node route hook so `extensions/server_connect/routes/smart-image.js` now requires `@benpley/wappler-smart-image/routes/smart-image-routes`\n- Kept the legacy alias file `extensions/server_connect/smart-image.js` pointing to `./routes/smart-image`\n\n### Previously included in 1.0.1\n\n**Server-side (Node.js / Sharp)**\n- On-the-fly image resizing and format conversion (AVIF, WebP, JPEG, PNG) via [Sharp](https://sharp.pixelplumbing.com/)\n- Automatic cache invalidation by comparing source and cache file `mtime` — no manual version strings needed\n- Concurrency lock prevents duplicate generation when multiple requests arrive for the same variant simultaneously\n- Auto-downscale of oversized source images that exceed the configured `maxWidth` / `maxHeight`\n- Optional cache warm-up on server start (`warmOnStart`) to pre-generate variants for all configured source folders\n- Orphan cache cleanup utility — removes cached variants whose source images no longer exist\n\n**Client-side (`<smart-img>` custom element)**\n- Breakpoint-aware image selection (desktop / tablet / mobile) with configurable breakpoint thresholds\n- Retina / HiDPI support — 2× (or custom multiplier) variants served automatically\n- Blur-up placeholder with three modes: `auto` (generated), `manual` (custom image), `none`\n- Optional fallback image when the optimised variant fails to load\n- `display-size` attribute: `responsive` (fills available width) or `original` (natural size)\n- Reacts to viewport resize — swaps to the correct variant dynamically\n\n**Wappler integration**\n- Visual editor tile under **Media** with full property panel and App Connect data-binding support for `alt`\n- `app/config/smart-image.json` scaffolded automatically on install\n- Express route hook registered at startup — no manual wiring required\n\n---\n\n## License\n\nMIT — developed for the Wappler community.\n","readmeFilename":"README.md"}