lake-cimg 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -34
- package/bin/cimg.js +1 -1
- package/lib/compress.js +59 -6
- package/lib/scan.js +5 -0
- package/lib/scanCodeReferences.js +1 -1
- package/package.json +4 -7
- package/skills/README.md +0 -40
- package/skills/cimg-audit/SKILL.md +0 -63
- package/skills/cimg-audit/reference.md +0 -49
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
- 处理过程输出体积对比;失败时非零退出码
|
|
11
11
|
- **`picture` 子命令**:从单张 PNG/JPEG/WebP 源图一次生成 **AVIF + WebP + JPEG**,配合前端 `<picture>` 做渐进增强(不支持 GIF 动图源)
|
|
12
12
|
- **`scan-code` 子命令**:扫描源码中的图片引用并结合真实像素尺寸给出 CLS / 比例等建议(只读)
|
|
13
|
-
- **Agent Skill**:通过 [`npx skills add`](https://www.npmjs.com/package/skills) 安装 [`skills/cimg-audit`](skills/cimg-audit/SKILL.md)(技能名 **`cimg-audit`**,好记),在 Cursor 等环境里用自然语言驱动 `npx lake-cimg@latest …`
|
|
14
13
|
|
|
15
14
|
## 环境要求
|
|
16
15
|
|
|
@@ -156,38 +155,6 @@ npx lake-cimg@latest scan-code [path] [--no-recursive] [--limit <n>] [--issues-o
|
|
|
156
155
|
|
|
157
156
|
适合在构建脚本或 Node 服务中复用同一套逻辑。
|
|
158
157
|
|
|
159
|
-
## Agent Skill(Skills CLI / `npx skills add`)
|
|
160
|
-
|
|
161
|
-
本仓库按 [Skills CLI](https://www.npmjs.com/package/skills) 约定提供可安装 Skill,见目录 [`skills/`](skills/README.md)(与 [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills) 的 `skills/<name>/SKILL.md` 布局一致)。技能目录名为 **`cimg-audit`**(短、与包名一致,便于 `-s cimg-audit`)。
|
|
162
|
-
|
|
163
|
-
**何时需要 / 触发条件(选用本 Skill 的典型场景)**
|
|
164
|
-
|
|
165
|
-
- 对话或任务涉及:**图片体积与格式**(WebP/AVIF)、**`<picture>` / `srcset`**、**LCP / CLS / layout shift**、首屏大图
|
|
166
|
-
- 要先做**只读**引用审计再改代码:用 **`npx lake-cimg@latest scan-code`** 出 JSON,再按需改标签或压缩
|
|
167
|
-
- 希望 Agent **按固定流程**:先 `scan-code` → 再按需 `npx lake-cimg@latest` 压缩或 `picture` 多格式输出
|
|
168
|
-
|
|
169
|
-
更细的英文说明见 [`skills/README.md` 的「When to use」一节](skills/README.md#when-to-use)。
|
|
170
|
-
|
|
171
|
-
**从 GitHub 安装**(需已推送;将 `lake0090/lake-cimg` 换成你的 fork 若不同):
|
|
172
|
-
|
|
173
|
-
```bash
|
|
174
|
-
# 安装到用户级(-g),仅本 skill(-s),跳过确认(-y)
|
|
175
|
-
npx skills add lake0090/lake-cimg -s cimg-audit -g -y
|
|
176
|
-
|
|
177
|
-
# 仅安装到当前项目
|
|
178
|
-
npx skills add lake0090/lake-cimg -s cimg-audit -y
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**本地克隆开发时**(在仓库根目录执行):
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
npx skills add . -s cimg-audit -y
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
可选:`--agent cursor` 等,见 `npx skills add --help`。Skill 正文在 [`skills/cimg-audit/SKILL.md`](skills/cimg-audit/SKILL.md),指导用 **`npx lake-cimg@latest scan-code`** 与压缩 / `picture` 子命令配合使用。
|
|
188
|
-
|
|
189
|
-
安装 Skill 后,在 Agent 对话里可直接让模型按 Skill 流程执行(例如:先 `scan-code` 再按需压缩);CLI 与 `npx` 均在本地执行,图片不会上传到云端。
|
|
190
|
-
|
|
191
158
|
## 常见问题
|
|
192
159
|
|
|
193
160
|
- **写权限**:`-o` 指向的目录需可创建/写入;否则 sharp 或 `fs` 会报错。
|
|
@@ -198,7 +165,7 @@ npx skills add . -s cimg-audit -y
|
|
|
198
165
|
|
|
199
166
|
- **Lighthouse 审计**:后续接入 [Lighthouse](https://developer.chrome.com/docs/lighthouse)(或 CI 中的 Lighthouse CI),对典型页面做性能 / 最佳实践等审计。
|
|
200
167
|
- **审计前后对比**:在引入 `scan-code`、压缩、`picture` 等优化前后各跑一轮,保存报告(JSON/HTML),对比 LCP、CLS、资源体积等指标,量化改动效果。
|
|
201
|
-
- **专项优化**:根据 Lighthouse 报告中的具体项(如 LCP 候选、未使用 CSS、图片尺寸等)做针对性迭代,与现有 CLI
|
|
168
|
+
- **专项优化**:根据 Lighthouse 报告中的具体项(如 LCP 候选、未使用 CSS、图片尺寸等)做针对性迭代,与现有 CLI 工作流互补。
|
|
202
169
|
|
|
203
170
|
## 发布到 npm(维护者)
|
|
204
171
|
|
package/bin/cimg.js
CHANGED
|
@@ -298,7 +298,7 @@ program
|
|
|
298
298
|
.command("scan [input]")
|
|
299
299
|
.description("扫描图片并给出优化建议(不修改文件)")
|
|
300
300
|
.option("-r, --recursive", "递归处理子目录")
|
|
301
|
-
.option("--json", "以 JSON
|
|
301
|
+
.option("--json", "以 JSON 格式输出(适合脚本与工具消费)")
|
|
302
302
|
.action(async function scanAction(input, opts) {
|
|
303
303
|
if (!input || input.trim() === "") {
|
|
304
304
|
this.outputHelp();
|
package/lib/compress.js
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
encodeJpegBuffer,
|
|
18
18
|
} from "./sharpHelpers.js";
|
|
19
19
|
|
|
20
|
+
/** 小于此体积跳过压缩 */
|
|
21
|
+
const THRESHOLD_SKIP_FOR_AGENT = 10 * 1024;
|
|
22
|
+
|
|
20
23
|
/** Max concurrent tasks for processing images */
|
|
21
24
|
const MAX_CONCURRENCY = Math.max(1, cpus().length);
|
|
22
25
|
|
|
@@ -104,7 +107,9 @@ export async function collectFiles(inputPath, recursive = false) {
|
|
|
104
107
|
* originalHeight: number | null,
|
|
105
108
|
* width: number | null,
|
|
106
109
|
* height: number | null,
|
|
107
|
-
* format: string | null
|
|
110
|
+
* format: string | null,
|
|
111
|
+
* skipped?: true,
|
|
112
|
+
* reason?: "small" | "larger"
|
|
108
113
|
* }>}
|
|
109
114
|
*/
|
|
110
115
|
export async function processOne(inputPath, options = {}) {
|
|
@@ -119,6 +124,21 @@ export async function processOne(inputPath, options = {}) {
|
|
|
119
124
|
|
|
120
125
|
const inputBuffer = await readFile(inputPath);
|
|
121
126
|
const sizeBefore = inputBuffer.length;
|
|
127
|
+
if (sizeBefore < THRESHOLD_SKIP_FOR_AGENT) {
|
|
128
|
+
return {
|
|
129
|
+
outPath,
|
|
130
|
+
sizeBefore,
|
|
131
|
+
sizeAfter: sizeBefore,
|
|
132
|
+
originalWidth: null,
|
|
133
|
+
originalHeight: null,
|
|
134
|
+
width: null,
|
|
135
|
+
height: null,
|
|
136
|
+
format: null,
|
|
137
|
+
skipped: true,
|
|
138
|
+
reason: "small",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
let pipeline = sharp(inputBuffer, { animated: true });
|
|
123
143
|
const inputMeta = await pipeline.metadata();
|
|
124
144
|
|
|
@@ -128,8 +148,23 @@ export async function processOne(inputPath, options = {}) {
|
|
|
128
148
|
? await encodeWebpBuffer(pipeline, quality, { lossless: false })
|
|
129
149
|
: await toFormatBuffer(pipeline, ext, quality);
|
|
130
150
|
|
|
131
|
-
const outputMeta = await sharp(outputBuffer).metadata();
|
|
132
151
|
const sizeAfter = outputBuffer.length;
|
|
152
|
+
if (sizeAfter > sizeBefore) {
|
|
153
|
+
return {
|
|
154
|
+
outPath,
|
|
155
|
+
sizeBefore,
|
|
156
|
+
sizeAfter,
|
|
157
|
+
originalWidth: inputMeta.width ?? null,
|
|
158
|
+
originalHeight: inputMeta.height ?? null,
|
|
159
|
+
width: null,
|
|
160
|
+
height: null,
|
|
161
|
+
format: null,
|
|
162
|
+
skipped: true,
|
|
163
|
+
reason: "larger",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const outputMeta = await sharp(outputBuffer).metadata();
|
|
133
168
|
await mkdir(outBase, { recursive: true });
|
|
134
169
|
await writeFile(outPath, outputBuffer);
|
|
135
170
|
return {
|
|
@@ -166,7 +201,7 @@ async function toFormatBuffer(pipeline, ext, quality) {
|
|
|
166
201
|
/**
|
|
167
202
|
* Run batch compression.
|
|
168
203
|
* @param {{ inputPath: string, outDir?: string | null, size?: number | null, quality?: number, recursive?: boolean }} options
|
|
169
|
-
* @returns {Promise<{ success: number, failed: number }>}
|
|
204
|
+
* @returns {Promise<{ success: number, failed: number, skipped: number }>} counts; throws if input invalid
|
|
170
205
|
*/
|
|
171
206
|
export async function run(options) {
|
|
172
207
|
const {
|
|
@@ -208,13 +243,30 @@ export async function run(options) {
|
|
|
208
243
|
|
|
209
244
|
let success = 0;
|
|
210
245
|
let failed = 0;
|
|
246
|
+
let skipped = 0;
|
|
211
247
|
let totalBefore = 0;
|
|
212
248
|
let totalAfter = 0;
|
|
213
249
|
for (let i = 0; i < files.length; i++) {
|
|
214
250
|
const r = results[i];
|
|
215
251
|
const fp = files[i];
|
|
216
252
|
if (r.status === "fulfilled") {
|
|
217
|
-
const
|
|
253
|
+
const v = r.value;
|
|
254
|
+
if (v.skipped) {
|
|
255
|
+
skipped++;
|
|
256
|
+
if (v.reason === "small") {
|
|
257
|
+
console.log(` ⏭ ${fp}`);
|
|
258
|
+
console.log(
|
|
259
|
+
` 不处理:原图 ${formatSize(v.sizeBefore)} 小于 10KB(无需压缩)`
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
console.log(` ⏭ ${fp}`);
|
|
263
|
+
console.log(
|
|
264
|
+
` 不处理:压缩后 ${formatSize(v.sizeAfter)} 大于原图 ${formatSize(v.sizeBefore)}(已保留原图、未写出新文件)`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const { outPath, sizeBefore, sizeAfter } = v;
|
|
218
270
|
totalBefore += sizeBefore;
|
|
219
271
|
totalAfter += sizeAfter;
|
|
220
272
|
console.log(` ✅ ${fp} → ${basename(outPath)}`);
|
|
@@ -225,9 +277,10 @@ export async function run(options) {
|
|
|
225
277
|
failed++;
|
|
226
278
|
}
|
|
227
279
|
}
|
|
228
|
-
|
|
280
|
+
const skipPart = skipped > 0 ? `,跳过 ${skipped}(<10KB 或压缩后更大)` : "";
|
|
281
|
+
console.log(`\n完成:成功 ${success},失败 ${failed}${skipPart}。`);
|
|
229
282
|
if (success > 0 && totalBefore > 0) {
|
|
230
283
|
console.log(`合计:${formatCompare(totalBefore, totalAfter)}`);
|
|
231
284
|
}
|
|
232
|
-
return { success, failed };
|
|
285
|
+
return { success, failed, skipped };
|
|
233
286
|
}
|
package/lib/scan.js
CHANGED
|
@@ -7,6 +7,8 @@ import sharp from "sharp";
|
|
|
7
7
|
import { collectFiles } from "./compress.js";
|
|
8
8
|
|
|
9
9
|
const KB = 1024;
|
|
10
|
+
/** 小于此体积一般无压缩收益,建议标记为不处理 */
|
|
11
|
+
const THRESHOLD_SKIP_FOR_AGENT = 10 * KB;
|
|
10
12
|
const THRESHOLD_LARGE_NON_WEBP = 100 * KB;
|
|
11
13
|
const THRESHOLD_LARGE_WEBP = 500 * KB;
|
|
12
14
|
const MAX_EDGE = 1920;
|
|
@@ -25,6 +27,9 @@ function formatSize(bytes) {
|
|
|
25
27
|
* @returns {string}
|
|
26
28
|
*/
|
|
27
29
|
function computeSuggestion(sizeBytes, format, width, height) {
|
|
30
|
+
if (sizeBytes < THRESHOLD_SKIP_FOR_AGENT) {
|
|
31
|
+
return "不处理:原图 <10KB(体积过小,无需压缩)";
|
|
32
|
+
}
|
|
28
33
|
const fmt = (format ?? "").toLowerCase();
|
|
29
34
|
if (fmt !== "webp" && sizeBytes > THRESHOLD_LARGE_NON_WEBP) {
|
|
30
35
|
return "convert to webp";
|
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lake-cimg",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Batch image compression to WebP — CLI
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Batch image compression to WebP — CLI with picture stack and scan-code for frontend image audit",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image",
|
|
7
7
|
"compress",
|
|
8
8
|
"webp",
|
|
9
9
|
"sharp",
|
|
10
|
-
"
|
|
11
|
-
"skills",
|
|
12
|
-
"cursor"
|
|
10
|
+
"cli"
|
|
13
11
|
],
|
|
14
12
|
"repository": {
|
|
15
13
|
"type": "git",
|
|
@@ -23,8 +21,7 @@
|
|
|
23
21
|
"files": [
|
|
24
22
|
"bin",
|
|
25
23
|
"lib",
|
|
26
|
-
"README.md"
|
|
27
|
-
"skills"
|
|
24
|
+
"README.md"
|
|
28
25
|
],
|
|
29
26
|
"publishConfig": {
|
|
30
27
|
"access": "public"
|
package/skills/README.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Skills (Skills CLI)
|
|
2
|
-
|
|
3
|
-
This directory follows the layout expected by **[Skills CLI](https://www.npmjs.com/package/skills)** (`npx skills add`), same idea as [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills): each subfolder is one skill with a `SKILL.md`.
|
|
4
|
-
|
|
5
|
-
| Skill | Summary |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| [cimg-audit](./cimg-audit/SKILL.md) | Audit `<img>` / Pug `img()` / imports / `url()` for CLS, aspect ratio, and format hints via `npx lake-cimg@latest scan-code` |
|
|
8
|
-
|
|
9
|
-
### When to use
|
|
10
|
-
|
|
11
|
-
Skill id **`cimg-audit`** — use it when the conversation or task touches:
|
|
12
|
-
|
|
13
|
-
- **Performance / layout:** CLS, layout shift, LCP, hero images, `fetchPriority`
|
|
14
|
-
- **Markup / assets:** `<img>` dimensions, `aspect-ratio`, `<picture>`, `srcset` / `sizes`, WebP / AVIF
|
|
15
|
-
- **Workflow:** run **`scan-code`** first (read-only JSON), then fix markup and/or compress with **`npx lake-cimg@latest`** / **`picture`**
|
|
16
|
-
|
|
17
|
-
The skill name `cimg-audit` is short for discovery; the YAML `description` in `SKILL.md` is what agents match against.
|
|
18
|
-
|
|
19
|
-
Install from GitHub (after push):
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx skills add lake0090/lake-cimg -s cimg-audit -g -y
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Install only this repo’s skill into the **current project** (run from another repo):
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npx skills add lake0090/lake-cimg -s cimg-audit -y
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Develop locally from a clone of this repository:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
cd /path/to/lake-cimg
|
|
35
|
-
npx skills add . -s cimg-audit -y
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Use `-g` for a user-level install (`~/.cursor/skills/` etc., depending on agent). See `npx skills add --help` for `--agent` (e.g. `cursor`).
|
|
39
|
-
|
|
40
|
-
Browse the registry at [skills.sh](https://skills.sh/).
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cimg-audit
|
|
3
|
-
description: >-
|
|
4
|
-
Audits HTML, Vue, Pug, JS, TS, TSX, and JSX image references against intrinsic
|
|
5
|
-
dimensions for CLS risk, aspect-ratio mismatches, and modern-format hints.
|
|
6
|
-
Use when optimizing images, fixing layout shift, LCP heroes, picture/srcset,
|
|
7
|
-
or running lake-cimg scan-code via npx lake-cimg@latest.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# cimg image audit
|
|
11
|
-
|
|
12
|
-
Run **`npx lake-cimg@latest`** from the **project root** (no global install).
|
|
13
|
-
|
|
14
|
-
<a id="invoke-scan-code"></a>
|
|
15
|
-
|
|
16
|
-
## Invoke `scan-code`
|
|
17
|
-
|
|
18
|
-
Use **one** command starting with `npx` — **not** `cd … && npx …` (PowerShell 5.1 on Windows does not support `&&`).
|
|
19
|
-
|
|
20
|
-
| `path` | Behavior |
|
|
21
|
-
| --- | --- |
|
|
22
|
-
| *(omitted)* | Scans **`.`** (current working directory). Run from repo root to cover the tree. |
|
|
23
|
-
| **Directory** | Recursively scans supported sources under that folder (honors `--no-recursive`). |
|
|
24
|
-
| **Single file** | Only that file. Extension must be `.html`, `.htm`, `.vue`, `.pug`, `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, or `.jsx`. |
|
|
25
|
-
|
|
26
|
-
**Output:** stdout is **only** pretty-printed JSON: `items[]` with `issues`, `hints`, `snippet`, `intrinsicWidth` / `intrinsicHeight` when metadata was read, plus top-level `summary` and scan metadata. **`--issues-only`** drops rows with empty `issues`. For **`missing_dimensions`**, use **`intrinsicWidth` / `intrinsicHeight`** on the same item and/or the English hint that repeats them.
|
|
27
|
-
|
|
28
|
-
**Examples:**
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx lake-cimg@latest scan-code
|
|
32
|
-
npx lake-cimg@latest scan-code /absolute/path/to/src
|
|
33
|
-
npx lake-cimg@latest scan-code /absolute/path/to/about.pug
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
More flags: `npx lake-cimg@latest scan-code --help` (e.g. `--limit`, `--issues-only`, `--no-recursive`). Prefer an **absolute** `path` if the shell cwd may not be the repo root.
|
|
37
|
-
|
|
38
|
-
## Workflow
|
|
39
|
-
|
|
40
|
-
- [ ] **1. Scan (read-only):** `npx lake-cimg@latest scan-code [path]` — see **Invoke `scan-code`** above.
|
|
41
|
-
- [ ] **2. Triage:** Prefer **`hints`** for what to change; use **`issues`** codes to group or filter. Common codes: `missing_dimensions`, `aspect_ratio_mismatch`, `suggest_modern_format` (default: **single WebP** — point `src` / `srcset` at `.webp` after exporting; use **`<picture>`** with AVIF/WebP + legacy fallback **only if the user explicitly asks** for multi-format markup or old-browser JPEG/PNG fallback), `needs_manual_review`, `missing_src`, `cannot_resolve`, `cannot_read_metadata`.
|
|
42
|
-
- [ ] **3. Fix then optimize:** Apply markup using [reference.md](reference.md) (`<img>` first; `<picture>` only when the user requires it). For **all** raster refs that need format or responsive delivery, not only one hero row. Compress / emit WebP: `npx lake-cimg@latest <path> [options]`. **Optional** full stack for `<picture>` when requested: `npx lake-cimg@latest picture <input> -O <outDir>` (details: package [README.md](../../README.md)).
|
|
43
|
-
|
|
44
|
-
## Rules of thumb
|
|
45
|
-
|
|
46
|
-
- **Aspect ratio:** Match display ratio to intrinsic (w÷h), or use **`object-fit`** + explicit box / `aspect-ratio` for crop/letterbox.
|
|
47
|
-
- **CLS:** Add `width` and `height` to `<img>`, or use CSS `aspect-ratio`. For `missing_dimensions`, fill in the exact `intrinsicWidth` / `intrinsicHeight`.
|
|
48
|
-
- **Responsive:** **`srcset` + `sizes`** on `<img>` (works with a single WebP). **`<picture>`** only when the user wants multiple formats in HTML; each `<source type="…">` must match the real file type. Width variants: separate files or `picture … -s <px>` when building a stack.
|
|
49
|
-
- **LCP:** At most one hero per view: **`fetchPriority="high"`**, **`loading="eager"`**; lazy-load the rest.
|
|
50
|
-
- **Alt:** Describe content and purpose; no keyword stuffing; **`alt=""`** only for decorative images.
|
|
51
|
-
|
|
52
|
-
## What the scanner cannot resolve
|
|
53
|
-
|
|
54
|
-
Dynamic **`src`** without a static path → **`needs_manual_review`**. **`http(s):`**, **`data:`**, and path aliases (**`@/`**, **`~/`**, etc.) → **`cannot_resolve`** (no alias map reads; no network fetch).
|
|
55
|
-
|
|
56
|
-
If **`hints`** mention alias skip: use **`rawRef`** and map the alias via Vite/webpack/tsconfig/Nuxt config. With a **real filesystem path**, run `npx lake-cimg@latest scan <resolved-path>` on assets or re-run **`scan-code`** on markup that uses resolvable relative paths. If unresolved, triage without intrinsic dimensions.
|
|
57
|
-
|
|
58
|
-
## Other CLI (after scan-code)
|
|
59
|
-
|
|
60
|
-
| Command | Role |
|
|
61
|
-
| --- | --- |
|
|
62
|
-
| `npx lake-cimg@latest <path>` | Compress / WebP — see `npx lake-cimg@latest --help` (`-o`, `-s`, `-q`, `-r`). |
|
|
63
|
-
| `npx lake-cimg@latest picture <input> -O <outDir>` | One raster → AVIF + WebP + JPEG **when the user wants `<picture>` / multi-format**; not the default if single WebP is enough. |
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# Image markup templates
|
|
2
|
-
|
|
3
|
-
Supplement for [SKILL.md](SKILL.md). Copy-paste starting points; adjust paths, dimensions, and `sizes` to match your assets and layout.
|
|
4
|
-
|
|
5
|
-
## Basic `<img>`
|
|
6
|
-
|
|
7
|
-
**When:** Single raster URL; reserve space for CLS. **Check:** `width` / `height` match intrinsic pixels (or use `aspect-ratio` for fluid); meaningful `alt` or `alt=""` if decorative.
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<img
|
|
11
|
-
srcset="
|
|
12
|
-
maine-coon-nap-320w.webp 320w,
|
|
13
|
-
maine-coon-nap-480w.webp 480w,
|
|
14
|
-
maine-coon-nap-800w.webp 800w
|
|
15
|
-
"
|
|
16
|
-
sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px"
|
|
17
|
-
src="maine-coon-nap-800w.webp"
|
|
18
|
-
alt="A watercolor illustration of a maine coon napping leisurely in front of a fireplace"
|
|
19
|
-
/>
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## `<picture>` + `srcset` + `sizes`
|
|
23
|
-
|
|
24
|
-
**When:** AVIF/WebP stack with JPEG (or similar) fallback; responsive widths. **Check:** Each `<source type="…">` matches the real file type; `sizes` matches breakpoints; `<img>` is the final fallback with matching `srcset` / `sizes`.
|
|
25
|
-
|
|
26
|
-
```html
|
|
27
|
-
<picture>
|
|
28
|
-
<source
|
|
29
|
-
type="image/avif"
|
|
30
|
-
srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
|
|
31
|
-
sizes="(max-width: 600px) 100vw, 50vw"
|
|
32
|
-
/>
|
|
33
|
-
<source
|
|
34
|
-
type="image/webp"
|
|
35
|
-
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
|
|
36
|
-
sizes="(max-width: 600px) 100vw, 50vw"
|
|
37
|
-
/>
|
|
38
|
-
<img
|
|
39
|
-
src="hero-800.jpg"
|
|
40
|
-
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
|
|
41
|
-
sizes="(max-width: 600px) 100vw, 50vw"
|
|
42
|
-
width="1200"
|
|
43
|
-
height="600"
|
|
44
|
-
alt="Describe subject and purpose on this page"
|
|
45
|
-
loading="lazy"
|
|
46
|
-
decoding="async"
|
|
47
|
-
/>
|
|
48
|
-
</picture>
|
|
49
|
-
```
|