@imgly/html-exporter
v0.2.0
Published
Export CE.SDK scenes as responsive HTML5. Platform-agnostic TypeScript library for converting Creative Engine designs to HTML.
Readme
@imgly/html-exporter
Export CE.SDK scenes to HTML5 — static designs and animated video timelines.
Installation
npm install @imgly/html-exporterQuick Start
import CreativeEngine from "@cesdk/engine";
import { exportHtml } from "@imgly/html-exporter";
const engine = await CreativeEngine.init({ license: "YOUR_LICENSE" });
await engine.scene.loadFromArchiveURL("https://example.com/scene.zip");
const result = await exportHtml(engine, {
format: "external",
pageIndex: 0,
});
// Write files to disk (Node.js)
import fs from "fs/promises";
import path from "path";
for (const [filePath, file] of result.files) {
const fullPath = path.join("./output", filePath);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, file.content);
}
engine.dispose();API
exportHtml(engine, options)
Exports a single page from the loaded scene as HTML5.
const result = await exportHtml(engine, options);Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| pageIndex | number | required | 0-based page index to export |
| format | "external" \| "embedded" | "external" | Output format (see below) |
| quality | number | 90 | WebP image quality (0–100) |
| animated | boolean \| "auto" | "auto" | Generate GSAP timeline for animations |
| textMode | "html" \| "vector" | "html" | Text rendering mode |
| insertExplicitLineBreaks | boolean | true | Insert <br> at CE.SDK line break positions |
| useSyntheticIds | boolean | false | Use stable deterministic element IDs |
| abortSignal | AbortSignal | — | Cancel the export |
Returns
interface ExportHtmlResult {
files: FileMap; // Generated files (extends Map<string, ExportFile>)
messages?: LogMessage[]; // Log messages from export
}Output Formats
External — HTML file with separate image and font files. Best for production where assets can be cached independently.
const result = await exportHtml(engine, { format: "external", pageIndex: 0 });
// Files: index.html, images/*.webp, fonts/*.woff2Embedded — Single HTML file with all assets inlined as base64 data URLs. Best for sharing or previewing.
const result = await exportHtml(engine, { format: "embedded", pageIndex: 0 });
// Files: index.html (self-contained)Working with FileMap
The result contains a FileMap — a standard Map<string, ExportFile> with an added toZip() method.
const result = await exportHtml(engine, { format: "external", pageIndex: 0 });
// Read, add, modify, or remove files
result.files.get("index.html");
result.files.set("manifest.json", {
content: JSON.stringify({ version: "1.0" }),
mimeType: "application/json",
});
result.files.delete("unwanted.txt");
// Package as ZIP
const zip = await result.files.toZip();
await fs.writeFile("output.zip", zip);injectGsapPlayer(html, options?)
Injects GSAP from a CDN into exported HTML for quick preview and debugging. Adds autoplay looping by default.
import { exportHtml, injectGsapPlayer } from "@imgly/html-exporter";
const result = await exportHtml(engine, { format: "embedded", pageIndex: 0 });
const html = result.files.get("index.html")!.content as string;
// Inject GSAP + autoplay for preview
const playableHtml = injectGsapPlayer(html);Or customize:
// Custom GSAP URL, no autoplay
const playableHtml = injectGsapPlayer(html, {
gsapUrl: "https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js",
autoplay: false,
});Note:
injectGsapPlayerautomatically detects and injects SplitText from CDN when the exported HTML containsdata-requires-plugins="SplitText". You can override the URL or disable this with thesplitTextUrloption.
Animations with GSAP
Exported HTML for animated scenes (video mode) includes an inline runtime script that builds a GSAP timeline from data-animation attributes in the DOM. GSAP must be loaded as a global before the page renders.
How It Works
- The exporter embeds a lightweight runtime
<script>in the HTML. - The runtime reads
data-timeline-*anddata-animationattributes from elements. - It calls
gsap.timeline(),gsap.from(), andgsap.to()to build the animation. - If GSAP is not loaded, elements remain visible as a static fallback.
Detecting Dependencies
The exported HTML signals what it needs via data attributes on the #container div:
<!-- Static (no GSAP needed) -->
<div id="container" class="page">
<!-- Animated (GSAP required) -->
<div id="container" class="page" data-timeline-duration="5.0">
<!-- Animated with text segmentation (GSAP + SplitText required) -->
<div id="container" class="page" data-timeline-duration="5.0"
data-requires-plugins="SplitText">You can check these attributes programmatically:
const html = result.files.get("index.html")!.content as string;
const needsGsap = html.includes("data-timeline-duration");
const needsSplitText = html.includes('data-requires-plugins="SplitText"');Loading GSAP
The simplest approach is to use injectGsapPlayer(), which automatically injects both GSAP core and SplitText (when needed) from CDN:
import { exportHtml, injectGsapPlayer } from "@imgly/html-exporter";
const result = await exportHtml(engine, { format: "embedded", pageIndex: 0 });
const html = result.files.get("index.html")!.content as string;
// Injects GSAP + SplitText (if needed) + autoplay
const playableHtml = injectGsapPlayer(html);Or manually add script tags before the exported HTML's inline <script>:
<!-- GSAP core (required for all animated exports) -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<!-- SplitText plugin (only when data-requires-plugins="SplitText" is present) -->
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/SplitText.min.js"></script>Note: SplitText became free in GSAP 3.12.0 and is available on public CDNs.
Integration Example
import { exportHtml, injectGsapPlayer } from "@imgly/html-exporter";
const result = await exportHtml(engine, {
format: "embedded",
pageIndex: 0,
});
const htmlFile = result.files.get("index.html")!;
let html =
typeof htmlFile.content === "string"
? htmlFile.content
: new TextDecoder().decode(htmlFile.content);
// injectGsapPlayer auto-detects SplitText needs from data attributes
const playableHtml = injectGsapPlayer(html);
// Or with custom URLs:
const customHtml = injectGsapPlayer(html, {
gsapUrl: "/assets/js/gsap.min.js",
splitTextUrl: "/assets/js/SplitText.min.js",
});
// Or disable SplitText auto-injection:
const gsapOnlyHtml = injectGsapPlayer(html, { splitTextUrl: false });Text Animations and SplitText
Some text animations split text into individual lines, words, or characters for staggered effects. These require the GSAP SplitText plugin at runtime.
| Animation | Segments | Effect | |-----------|----------|--------| | Typewriter | Characters or words | Segments appear one at a time | | Spread | Characters | Characters spread out from center | | Merge | Words | Words slide in from alternating directions |
When SplitText is not loaded, these animations fall back to animating the entire text block as a unit — the animation still plays, but without the per-segment stagger.
Autoplay and Looping
The runtime builds the timeline but does not auto-play it. The timeline is exposed as window.__timeline for programmatic control:
<script>
// After GSAP and the exported HTML have loaded:
var tl = window.__timeline;
if (tl) {
tl.play(); // Play once
tl.eventCallback("onComplete", function () {
tl.seek(0);
tl.play(); // Loop
});
}
</script>Or use the built-in injectGsapPlayer() helper which does this automatically:
import { injectGsapPlayer } from "@imgly/html-exporter";
const playableHtml = injectGsapPlayer(html); // Injects GSAP CDN + autoplay loopCommon Workflows
Modifying the HTML
const result = await exportHtml(engine, { format: "external", pageIndex: 0 });
const htmlFile = result.files.get("index.html")!;
let html =
typeof htmlFile.content === "string"
? htmlFile.content
: new TextDecoder().decode(htmlFile.content);
// Add metadata, tracking scripts, etc.
html = html
.replace("</head>", '<meta name="author" content="My Company">\n</head>')
.replace("</body>", "<script>initTracking();</script>\n</body>");
result.files.set("index.html", { content: html, mimeType: "text/html" });Click Tags for Ad Platforms
const clickTagScript = `
<script>
var clickTag = "https://example.com";
document.addEventListener("click", function() {
window.open(clickTag, "_blank");
});
</script>`;
html = html.replace("</body>", clickTagScript + "\n</body>");Adding Custom Files
// Adobe TDF file
result.files.set("template.tdf", {
content: "NAME\tDATA_TYPE\nheadline\tTEXT\nimage\tIMAGE",
mimeType: "text/tab-separated-values",
});
// Platform manifest
result.files.set("manifest.json", {
content: JSON.stringify({ width: 300, height: 250, platform: "google-ads" }),
mimeType: "application/json",
});Exporting All Pages
const pages = engine.block.findByType("page");
for (let i = 0; i < pages.length; i++) {
const result = await exportHtml(engine, { format: "external", pageIndex: i });
const zip = await result.files.toZip();
await fs.writeFile(`page-${i + 1}.zip`, zip);
}Browser Preview
const result = await exportHtml(engine, { format: "embedded", pageIndex: 0 });
const html = result.files.get("index.html")!.content as string;
// Display in iframe
const iframe = document.getElementById("preview") as HTMLIFrameElement;
iframe.srcdoc = html;Changelog
See CHANGELOG.md for release notes.
License
ISC
