开发一个 Adobe Express 加载项(add-on)“Brand Swatches”:在面板里粘贴品牌色值,点一下就在当前页面插入一排色块。用官方 CLI 生成项目,改造成能校验输入、记住上次色板的版本,本地构建打包。
先看清成品
已在本机用
npx @adobe/create-ccweb-add-on@3.11.0生成项目(模板javascript-with-document-sandbox),改完后npm run build、npm run package均成功并生成dist.zip;色值解析的 Node 测试通过。本文环境没有登录 Adobe,没有在 Express 里加载运行加载项,也没有提交到市场;npm run start的本地 HTTPS 证书配置也没有执行。
注意:只使用你有权使用的照片、音乐、字体和标志。Adobe Stock 素材与 Firefly 生成内容的商用范围以 Adobe 条款和产品内标注为准(标为 beta 的生成功能可能只限个人用途)。不要共享账号,也不要用代理或改地区的方式绕过 Adobe 的服务地区限制。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 加载项面板 | 文本框 + 插入按钮 + 提示信息 |
| 文档沙箱 API | insertSwatches(hexList) 在页面上画色块 |
| 共用的解析模块 | UI 和沙箱都用,带测试 |
| 打包文件 | dist.zip,可用于提交 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| Node.js | 当前 LTS 版本(本文在本机 Node v22.23.3 上验证) | CLI 通过 npx 运行 |
| Adobe 账号 | 能登录 Express 网页版 | 在设置里打开 Add-on Development |
| 浏览器 | 桌面版 Chrome/Edge 等 | 本地开发服务器默认端口 5241 |
文件结构
18-adobe-express-C-src/brand-swatches/
├── package.json
├── tsconfig.json
├── test_palette.mjs # 解析测试
└── src/
├── index.html # 面板 UI
├── manifest.json
├── shared/palette.js # 解析品牌色(新增)
├── ui/index.js # UI 运行时
└── sandbox/code.js # 文档沙箱,操作文档
第 1 步:生成项目
npx @adobe/create-ccweb-add-on brand-swatches --template javascript-with-document-sandbox
cd brand-swatches
CLI 会先问是否发送分析数据,再问 SSL 证书怎么配置(自动生成或使用已有证书)。按需选择。
检查结果:出现 src/manifest.json、src/ui/index.js、src/sandbox/code.js。
生成的 manifest.json(testId 每次不同):
{
"testId": "ed271a45-d9f6-4c48-b8ad-4bfa3062b602",
"name": "Brand Swatches",
"version": "1.0.0",
"manifestVersion": 2,
"requirements": {
"apps": [
{
"name": "Express",
"apiVersion": 1
}
]
},
"entryPoints": [
{
"type": "panel",
"id": "panel1",
"main": "index.html",
"documentSandbox": "sandbox/code.js"
}
]
}
documentSandbox 指向沙箱代码;UI 运行在 iframe 里,不能直接改文档,必须通过沙箱暴露的 API。
第 2 步:写共用的解析模块
src/shared/palette.js:
// 纯函数:解析用户粘贴的品牌色,供 UI 校验与沙箱绘制共用
export function parsePalette(text) {
const ok = [], bad = [];
for (const raw of text.split(/[\s,,;;]+/).filter(Boolean)) {
const m = /^#?([0-9a-f]{6})$/i.exec(raw);
if (!m) { bad.push(raw); continue; }
const hex = "#" + m[1].toUpperCase();
if (!ok.includes(hex)) ok.push(hex);
}
return { ok: ok.slice(0, 12), bad, truncated: ok.length > 12 };
}
export function hexToRgb(hex) {
const n = parseInt(hex.slice(1), 16);
return { red: ((n >> 16) & 255) / 255, green: ((n >> 8) & 255) / 255, blue: (n & 255) / 255, alpha: 1 };
}
第 3 步:沙箱里画色块
src/sandbox/code.js:
import addOnSandboxSdk from "add-on-sdk-document-sandbox";
import { editor } from "express-document-sdk";
import { hexToRgb } from "../shared/palette.js";
const { runtime } = addOnSandboxSdk.instance;
runtime.exposeApi({
// 在当前页面左上角插入一排色块,每块 80×80,间距 12
insertSwatches(hexList) {
const parent = editor.context.insertionParent;
hexList.forEach((hex, i) => {
const r = editor.createRectangle();
r.width = 80;
r.height = 80;
r.translation = { x: 20 + i * 92, y: 20 };
r.fill = editor.makeColorFill(hexToRgb(hex));
parent.children.append(r);
});
return hexList.length;
}
});
editor.createRectangle()、editor.makeColorFill()、editor.context.insertionParent 都来自 Document APIs;颜色分量是 01 的小数,所以要把 0255 除以 255。
第 4 步:面板 UI
src/ui/index.js:
import addOnUISdk from "https://new.express.adobe.com/static/add-on-sdk/sdk.js";
import { parsePalette } from "../shared/palette.js";
addOnUISdk.ready.then(async () => {
const sandbox = await addOnUISdk.instance.runtime.apiProxy("documentSandbox");
const input = document.getElementById("colors");
const btn = document.getElementById("insert");
const msg = document.getElementById("msg");
input.value = localStorage.getItem("brand-colors") || "#1F3A5F #F4B400 #FFFFFF";
btn.addEventListener("click", async () => {
const { ok, bad, truncated } = parsePalette(input.value);
if (!ok.length) { msg.textContent = "没有合法色值,格式如 #1F3A5F"; return; }
localStorage.setItem("brand-colors", input.value);
const n = await sandbox.insertSwatches(ok);
msg.textContent = `已插入 ${n} 个色块` + (bad.length ? `;忽略:${bad.join(" ")}` : "") + (truncated ? ";超过 12 个已截断" : "");
});
btn.disabled = false;
});
src/index.html 里把模板的按钮换成:
<label for="colors">品牌色(空格或逗号分隔)</label>
<textarea id="colors" rows="3"></textarea>
<button id="insert" disabled>插入色块</button>
<p id="msg"></p>
按钮默认 disabled,等 SDK 就绪、沙箱代理拿到之后才启用,避免用户点了没反应。
第 5 步:测试与构建
test_palette.mjs:
import assert from "node:assert";
import { parsePalette, hexToRgb } from "./src/shared/palette.js";
const r = parsePalette("#1f3a5f, F4B400;#1F3A5F red #12345");
assert.deepStrictEqual(r.ok, ["#1F3A5F", "#F4B400"]);
assert.deepStrictEqual(r.bad, ["red", "#12345"]);
assert.deepStrictEqual(hexToRgb("#FF0000"), { red: 1, green: 0, blue: 0, alpha: 1 });
console.log("palette tests passed");
node test_palette.mjs
npm run build
npm run package
本机实际输出:
palette tests passed
Cleaning output directory dist/ ...
Done.
Building source directory src/ to dist/ ...
Done.
Creating a zip from dist/ ...
Done.
Node 会提示“Reparsing as ES module”,这是因为 package.json 没写
"type": "module",不影响测试结果。本文没有验证加上"type": "module"对 CLI 构建的影响,所以保持模板原样。
第 6 步:在 Express 里加载(本文未执行)
- 运行
npm run start,本地服务默认在 https://localhost:5241。 - 在 Adobe Express 设置中打开 Add-on Development。
- 左侧 Add-ons → Your add-ons → Add-on testing,连接本地加载项。
- 粘贴
#1F3A5F #F4B400 #FFFFFF,点“插入色块”。
预期结果:页面左上角出现 3 个 80×80 色块,提示“已插入 3 个色块”。改代码后会自动重新构建并刷新。
调试示例
输入 #1f3a5f, F4B400;red #12345,提示应为:
已插入 2 个色块;忽略:red #12345
小写会统一成大写、缺 # 会补上、重复会去掉,最多 12 个。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
| 浏览器提示证书不受信任 | 本地 HTTPS 证书没配置好 | 重新运行 CLI 的 SSL 设置,或按文档手动信任证书 |
| 面板按钮一直是灰的 | SDK 没就绪或 apiProxy 报错 | 打开浏览器开发者工具看控制台 |
| 色块颜色不对 | 直接用了 0~255 | 用 hexToRgb 转成 0~1 |
| UI 里调用 editor 报错 | UI 运行时不能直接访问文档 | 文档操作只写在 sandbox/code.js |
| 构建后 shared 模块找不到 | 路径写错 | 沙箱和 UI 都用 ../shared/palette.js 相对路径 |
模板规则
- 文档操作只放沙箱,UI 只做输入和提示。
- 纯逻辑抽到 shared,写测试。
- 提交前
npm run build && npm run package,用 dist.zip 提交。
项目资源
| 用途 | 地址 |
|---|---|
| Hello World 入门(创建、构建、本地测试) | https://developer.adobe.com/express/add-ons/docs/guides/getting-started/hello-world |
| 项目结构与 CLI 模板 | https://developer.adobe.com/express/add-ons/docs/guides/getting-started/addon-project-anatomy |
| Document APIs | https://developer.adobe.com/express/add-ons/docs/references/document-sandbox/document-apis/ |
| create-ccweb-add-on 源码 | https://github.com/adobe/create-ccweb-add-on |
这个加载项只做一件小事,但覆盖了 Express 加载项开发的完整链路:UI 运行时收输入,通过代理调用文档沙箱,沙箱用 Document APIs 改文档。把这条链路跑通,再做更复杂的功能就只是加 API。