loopwind 0.18.1 → 0.20.1

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.
Files changed (69) hide show
  1. package/README.md +83 -0
  2. package/dist/commands/preview.d.ts.map +1 -1
  3. package/dist/commands/preview.js +2 -1
  4. package/dist/commands/preview.js.map +1 -1
  5. package/dist/commands/render.d.ts.map +1 -1
  6. package/dist/commands/render.js +2 -0
  7. package/dist/commands/render.js.map +1 -1
  8. package/dist/default-templates/AGENTS.md +54 -0
  9. package/dist/lib/renderer.d.ts.map +1 -1
  10. package/dist/lib/renderer.js +3 -0
  11. package/dist/lib/renderer.js.map +1 -1
  12. package/dist/lib/tailwind-browser.d.ts.map +1 -1
  13. package/dist/lib/tailwind-browser.js +169 -6
  14. package/dist/lib/tailwind-browser.js.map +1 -1
  15. package/dist/lib/tailwind.d.ts.map +1 -1
  16. package/dist/lib/tailwind.js +178 -7
  17. package/dist/lib/tailwind.js.map +1 -1
  18. package/dist/lib/video-preview.d.ts +1 -1
  19. package/dist/lib/video-preview.d.ts.map +1 -1
  20. package/dist/lib/video-preview.js +266 -249
  21. package/dist/lib/video-preview.js.map +1 -1
  22. package/dist/lib/video-renderer.d.ts +2 -0
  23. package/dist/lib/video-renderer.d.ts.map +1 -1
  24. package/dist/lib/video-renderer.js +4 -4
  25. package/dist/lib/video-renderer.js.map +1 -1
  26. package/dist/sdk/compiler.d.ts +94 -0
  27. package/dist/sdk/compiler.d.ts.map +1 -0
  28. package/dist/sdk/compiler.js +122 -0
  29. package/dist/sdk/compiler.js.map +1 -0
  30. package/dist/sdk/index.d.ts +3 -1
  31. package/dist/sdk/index.d.ts.map +1 -1
  32. package/dist/sdk/index.js +2 -1
  33. package/dist/sdk/index.js.map +1 -1
  34. package/dist/sdk/preview.d.ts +65 -0
  35. package/dist/sdk/preview.d.ts.map +1 -0
  36. package/dist/sdk/preview.js +262 -0
  37. package/dist/sdk/preview.js.map +1 -0
  38. package/dist/sdk/template.d.ts +47 -24
  39. package/dist/sdk/template.d.ts.map +1 -1
  40. package/dist/sdk/template.js +53 -93
  41. package/dist/sdk/template.js.map +1 -1
  42. package/examples/nextjs-template-import.ts +2 -2
  43. package/examples/sdk-video-preview.tsx +120 -0
  44. package/examples/template-compiler-workflow.ts +251 -0
  45. package/package.json +6 -2
  46. package/render-examples-600x400.mjs +161 -0
  47. package/render-spring-variants-fixed.mjs +60 -0
  48. package/render-staggered-text.mjs +56 -0
  49. package/test-jsx-support.mjs +32 -6
  50. package/test-sdk-config.mjs +138 -81
  51. package/test-sdk-source-config.mjs +427 -0
  52. package/test-static-debug.tsx +19 -0
  53. package/test-templates/config-test.mjs +17 -0
  54. package/test-templates/test-sdk.mjs +46 -22
  55. package/test-video-props.json +3 -0
  56. package/website/DEPLOYMENT.md +1 -0
  57. package/website/OG_IMAGES.md +1 -0
  58. package/website/astro.config.mjs +18 -2
  59. package/website/dist/.gitkeep +1 -0
  60. package/website/dist/_worker.js/index.js +1 -1
  61. package/website/dist/_worker.js/{manifest_BAAoOzaU.mjs → manifest_CT_D-YDe.mjs} +1 -1
  62. package/website/dist/llm.txt +1 -1
  63. package/website/dist/sdk/index.html +405 -102
  64. package/website/dist/sitemap.xml +12 -12
  65. package/website/package-lock.json +2866 -7080
  66. package/website/package.json +1 -2
  67. package/website/public/.gitkeep +1 -0
  68. package/website/templates/og-image.tsx +20 -21
  69. package/website/test-playground.mjs +45 -0
@@ -0,0 +1,262 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { renderToSVG } from '../lib/renderer.js';
3
+ import { templateToMeta } from './template.js';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ /**
8
+ * Pre-render all frames of a video template for preview
9
+ * Call this on the server or at build time
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const template = defineTemplate({
14
+ * name: 'intro',
15
+ * type: 'video',
16
+ * size: { width: 1920, height: 1080 },
17
+ * video: { fps: 30, duration: 3 },
18
+ * render: ({ tw, progress }) => <div style={tw('flex')}>Frame</div>
19
+ * });
20
+ *
21
+ * const frames = await previewVideo(template, { title: 'Hello' });
22
+ * // Pass to <VideoPreview frames={frames} />
23
+ * ```
24
+ */
25
+ export async function previewVideo(template, props, options = {}) {
26
+ const { config, format = 'svg' } = options;
27
+ const meta = templateToMeta(template);
28
+ if (meta.type !== 'video') {
29
+ throw new Error('Cannot preview image template. Only video templates are supported.');
30
+ }
31
+ if (!meta.video) {
32
+ throw new Error('Template is missing video metadata (fps, duration)');
33
+ }
34
+ const { fps, duration } = meta.video;
35
+ const totalFrames = Math.floor(fps * duration);
36
+ const frames = [];
37
+ // Save template to temp file so we can use the existing renderer
38
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'loopwind-preview-'));
39
+ const templatePath = path.join(tempDir, 'template.tsx');
40
+ try {
41
+ // Write template component with embedded meta
42
+ const templateContent = `import React from 'react';
43
+
44
+ const h = React.createElement;
45
+
46
+ export const meta = ${JSON.stringify(meta, null, 2)};
47
+
48
+ export default ${template.render.toString()};`;
49
+ await fs.writeFile(templatePath, templateContent);
50
+ // Merge configs
51
+ const mergedConfig = {
52
+ ...template.config,
53
+ ...config,
54
+ colors: { ...template.config?.colors, ...config?.colors },
55
+ fonts: { ...template.config?.fonts, ...config?.fonts },
56
+ };
57
+ // Render each frame
58
+ for (let frame = 0; frame < totalFrames; frame++) {
59
+ const progress = frame / totalFrames;
60
+ const svg = await renderToSVG(templatePath, { ...props, frame, progress }, { config: mergedConfig });
61
+ if (format === 'data-url') {
62
+ // Convert SVG to data URL for easier browser usage
63
+ const dataUrl = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
64
+ frames.push(dataUrl);
65
+ }
66
+ else {
67
+ frames.push(svg);
68
+ }
69
+ }
70
+ return {
71
+ frames,
72
+ meta: {
73
+ name: meta.name,
74
+ width: meta.size.width,
75
+ height: meta.size.height,
76
+ fps,
77
+ duration,
78
+ totalFrames,
79
+ },
80
+ };
81
+ }
82
+ finally {
83
+ // Cleanup temp files
84
+ await fs.rm(tempDir, { recursive: true, force: true });
85
+ }
86
+ }
87
+ /**
88
+ * Video preview component that displays pre-rendered frames
89
+ * Works in browser - no Node.js dependencies
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * // Server-side (Next.js, etc.)
94
+ * const frames = await previewVideo(template, props);
95
+ *
96
+ * // Client-side
97
+ * <VideoPreview frames={frames} autoPlay loop />
98
+ * ```
99
+ */
100
+ export function VideoPreview({ frames: previewFrames, autoPlay = true, loop = true, controls = true, className, style, onFrameChange, }) {
101
+ const [frame, setFrame] = useState(0);
102
+ const [isPlaying, setIsPlaying] = useState(autoPlay);
103
+ const [isLooping, setIsLooping] = useState(loop);
104
+ const animationRef = useRef(null);
105
+ const lastTimeRef = useRef(0);
106
+ const { frames, meta } = previewFrames;
107
+ const { fps, duration, totalFrames, width, height } = meta;
108
+ const durationMs = duration * 1000;
109
+ // Notify parent of frame changes
110
+ useEffect(() => {
111
+ if (onFrameChange) {
112
+ onFrameChange(Math.floor(frame));
113
+ }
114
+ }, [frame, onFrameChange]);
115
+ // Playback loop
116
+ useEffect(() => {
117
+ if (!isPlaying) {
118
+ if (animationRef.current) {
119
+ cancelAnimationFrame(animationRef.current);
120
+ animationRef.current = null;
121
+ }
122
+ return;
123
+ }
124
+ lastTimeRef.current = performance.now();
125
+ const animate = (time) => {
126
+ const delta = time - lastTimeRef.current;
127
+ lastTimeRef.current = time;
128
+ setFrame((prev) => {
129
+ const increment = (delta / 1000) * fps;
130
+ const next = prev + increment;
131
+ if (next >= totalFrames) {
132
+ if (isLooping) {
133
+ return next % totalFrames;
134
+ }
135
+ else {
136
+ setIsPlaying(false);
137
+ return totalFrames - 1;
138
+ }
139
+ }
140
+ return next;
141
+ });
142
+ animationRef.current = requestAnimationFrame(animate);
143
+ };
144
+ animationRef.current = requestAnimationFrame(animate);
145
+ return () => {
146
+ if (animationRef.current) {
147
+ cancelAnimationFrame(animationRef.current);
148
+ }
149
+ };
150
+ }, [isPlaying, fps, totalFrames, isLooping]);
151
+ const currentMs = (frame / totalFrames) * durationMs;
152
+ const progress = (frame / totalFrames) * 100;
153
+ const frameNum = Math.floor(frame);
154
+ const handleScrub = (e) => {
155
+ const value = parseFloat(e.target.value);
156
+ setFrame((value / 100) * totalFrames);
157
+ };
158
+ const handlePlayPause = () => {
159
+ if (!isPlaying && frame >= totalFrames - 1) {
160
+ setFrame(0);
161
+ }
162
+ setIsPlaying(!isPlaying);
163
+ };
164
+ const handleRestart = () => {
165
+ setFrame(0);
166
+ setIsPlaying(true);
167
+ };
168
+ const handleStepBackward = () => {
169
+ setIsPlaying(false);
170
+ setFrame((prev) => Math.max(0, prev - 1));
171
+ };
172
+ const handleStepForward = () => {
173
+ setIsPlaying(false);
174
+ setFrame((prev) => Math.min(totalFrames - 1, prev + 1));
175
+ };
176
+ return (React.createElement("div", { className: className, style: {
177
+ display: 'inline-flex',
178
+ flexDirection: 'column',
179
+ gap: '0.75rem',
180
+ ...style,
181
+ } },
182
+ React.createElement("div", { style: {
183
+ position: 'relative',
184
+ width,
185
+ height,
186
+ overflow: 'hidden',
187
+ borderRadius: '0.5rem',
188
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
189
+ } }, frames[frameNum]?.startsWith('data:') ? (React.createElement("img", { src: frames[frameNum], alt: `Frame ${frameNum}`, style: { display: 'block', width: '100%', height: '100%' } })) : (React.createElement("div", { dangerouslySetInnerHTML: { __html: frames[frameNum] || '' }, style: { width: '100%', height: '100%' } }))),
190
+ controls && (React.createElement("div", { style: {
191
+ display: 'flex',
192
+ flexDirection: 'column',
193
+ gap: '0.5rem',
194
+ width,
195
+ } },
196
+ React.createElement("input", { type: "range", min: "0", max: "100", step: "0.1", value: progress, onChange: handleScrub, style: {
197
+ width: '100%',
198
+ height: '6px',
199
+ background: `linear-gradient(to right, #7c3aed ${progress}%, #d1d5db ${progress}%)`,
200
+ borderRadius: '3px',
201
+ cursor: 'pointer',
202
+ WebkitAppearance: 'none',
203
+ appearance: 'none',
204
+ } }),
205
+ React.createElement("div", { style: {
206
+ display: 'flex',
207
+ alignItems: 'center',
208
+ justifyContent: 'space-between',
209
+ fontSize: '0.875rem',
210
+ } },
211
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '0.5rem' } },
212
+ React.createElement("button", { onClick: handleRestart, title: "Restart", style: {
213
+ background: 'transparent',
214
+ border: 'none',
215
+ cursor: 'pointer',
216
+ padding: '0.25rem',
217
+ fontSize: '1.25rem',
218
+ } }, "\u23EE"),
219
+ React.createElement("button", { onClick: handleStepBackward, title: "Previous frame", style: {
220
+ background: 'transparent',
221
+ border: 'none',
222
+ cursor: 'pointer',
223
+ padding: '0.25rem',
224
+ fontSize: '1.25rem',
225
+ } }, "\u23EA"),
226
+ React.createElement("button", { onClick: handlePlayPause, style: {
227
+ background: '#7c3aed',
228
+ border: 'none',
229
+ color: 'white',
230
+ cursor: 'pointer',
231
+ padding: '0.5rem 0.75rem',
232
+ borderRadius: '0.375rem',
233
+ fontSize: '1rem',
234
+ minWidth: '3rem',
235
+ } }, isPlaying ? '⏸' : '▶'),
236
+ React.createElement("button", { onClick: handleStepForward, title: "Next frame", style: {
237
+ background: 'transparent',
238
+ border: 'none',
239
+ cursor: 'pointer',
240
+ padding: '0.25rem',
241
+ fontSize: '1.25rem',
242
+ } }, "\u23E9"),
243
+ React.createElement("button", { onClick: () => setIsLooping(!isLooping), title: isLooping ? 'Loop enabled' : 'Loop disabled', style: {
244
+ background: isLooping ? '#e5e7eb' : 'transparent',
245
+ border: '1px solid #d1d5db',
246
+ cursor: 'pointer',
247
+ padding: '0.25rem 0.5rem',
248
+ borderRadius: '0.375rem',
249
+ fontSize: '0.875rem',
250
+ } }, "\uD83D\uDD01")),
251
+ React.createElement("div", { style: { fontFamily: 'monospace', color: '#6b7280' } },
252
+ Math.floor(currentMs),
253
+ "ms / ",
254
+ Math.floor(durationMs),
255
+ "ms",
256
+ React.createElement("span", { style: { marginLeft: '0.5rem' } },
257
+ "Frame ",
258
+ frameNum,
259
+ " / ",
260
+ totalFrames)))))));
261
+ }
262
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/sdk/preview.tsx"],"names":[],"mappings":"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"}
@@ -15,25 +15,6 @@ export interface TemplateDefinition<TProps = any> {
15
15
  config?: StyleConfig;
16
16
  render: (props: TProps & TemplateProps) => JSX.Element;
17
17
  }
18
- /**
19
- * Define a template programmatically for use in serverless environments
20
- *
21
- * @example
22
- * ```typescript
23
- * const ogImage = defineTemplate({
24
- * name: 'og-image',
25
- * type: 'image',
26
- * size: { width: 1200, height: 630 },
27
- * render: ({ tw, title, description }) => (
28
- * <div style={tw('flex flex-col w-full h-full bg-white p-12')}>
29
- * <h1 style={tw('text-6xl font-bold')}>{title}</h1>
30
- * <p style={tw('text-2xl text-gray-600')}>{description}</p>
31
- * </div>
32
- * )
33
- * });
34
- * ```
35
- */
36
- export declare function defineTemplate<TProps = any>(definition: TemplateDefinition<TProps>): TemplateDefinition<TProps>;
37
18
  /**
38
19
  * Convert a template definition to template metadata
39
20
  * @internal
@@ -42,27 +23,69 @@ export declare function templateToMeta(template: TemplateDefinition): TemplateMe
42
23
  /**
43
24
  * Define a template from an imported template file
44
25
  *
45
- * Allows reusing file-based templates from _loopwind/templates/ in the SDK
26
+ * The standard way to create templates in loopwind. Import a template file
27
+ * and pass it to defineTemplate() with optional config overrides.
46
28
  *
47
29
  * @example
48
30
  * ```typescript
49
- * import * as videoIntro from './_loopwind/templates/video-intro/template';
31
+ * import * as ogImage from './_loopwind/templates/og-image/template';
50
32
  *
51
- * const template = defineTemplateFromFile(videoIntro, {
33
+ * const template = defineTemplate(ogImage, {
52
34
  * config: {
53
35
  * colors: { primary: '#ff0000' }
54
36
  * }
55
37
  * });
56
38
  *
57
- * const mp4 = await renderVideo(template, { version: '2.0.0' });
39
+ * const png = await renderImage(template, { title: 'Hello' });
58
40
  * ```
59
41
  */
60
- export declare function defineTemplateFromFile<TProps = any>(templateModule: {
42
+ export declare function defineTemplate<TProps = any>(templateModule: {
61
43
  meta: TemplateMeta;
62
44
  default: (props: TProps & TemplateProps) => JSX.Element;
63
45
  }, options?: {
64
46
  config?: StyleConfig;
65
47
  }): TemplateDefinition<TProps>;
48
+ /**
49
+ * Define a template from a source code string
50
+ *
51
+ * **IMPORTANT:** This function expects **pre-compiled JavaScript** with React.createElement
52
+ * calls. If you have JSX source code, compile it first using `compileTemplate()` from
53
+ * 'loopwind/sdk/compiler'.
54
+ *
55
+ * **WARNING:** This function evaluates code strings using `new Function()`.
56
+ * Only use with **trusted** template sources. Never use with user input from
57
+ * untrusted sources as it can execute arbitrary code.
58
+ *
59
+ * **Recommended workflow:**
60
+ * 1. In your admin panel: Use `compileTemplate()` to transform JSX → JavaScript
61
+ * 2. Store the compiled JavaScript in your database
62
+ * 3. In production: Load compiled code and pass to `defineTemplateFromSource()`
63
+ *
64
+ * This approach keeps @babel/standalone out of your production bundle!
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // Admin panel: Compile and save
69
+ * import { compileTemplate } from 'loopwind/sdk/compiler';
70
+ *
71
+ * const jsxCode = getUserCodeFromEditor(); // Contains JSX
72
+ * const compiled = compileTemplate(jsxCode);
73
+ * await db.templates.save({ code: compiled });
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Production API: Load and render
79
+ * import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
80
+ *
81
+ * const template = await db.templates.findById(id);
82
+ * const templateDef = defineTemplateFromSource(template.code, {
83
+ * config: { colors: { primary: '#3b82f6' } }
84
+ * });
85
+ *
86
+ * const png = await renderImage(templateDef, { title: 'Hello' });
87
+ * ```
88
+ */
66
89
  export declare function defineTemplateFromSource<TProps = any>(sourceCode: string, options?: {
67
90
  config?: StyleConfig;
68
91
  }): TemplateDefinition<TProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKvD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC;AAEvC,MAAM,WAAW,kBAAkB,CAAC,MAAM,GAAG,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAC;CACxD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAG,GAAG,EACzC,UAAU,EAAE,kBAAkB,CAAC,MAAM,CAAC,GACrC,kBAAkB,CAAC,MAAM,CAAC,CAyB5B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CASzE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,GAAG,GAAG,EACjD,cAAc,EAAE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAA;CAAE,EAC/F,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CAW5B;AAuDD,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CAmC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,MAAM,EAAE,cAAc,GACrB,kBAAkB,CAAC,MAAM,CAAC,CA+D5B"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC;AAEvC,MAAM,WAAW,kBAAkB,CAAC,MAAM,GAAG,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAC;CACxD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,YAAY,CASzE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAG,GAAG,EACzC,cAAc,EAAE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,KAAK,GAAG,CAAC,OAAO,CAAA;CAAE,EAC/F,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CA0B5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GACrC,kBAAkB,CAAC,MAAM,CAAC,CAgC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,KAAK,CAAC;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,KAAK,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,GAAG,GAAG,EACnD,MAAM,EAAE,cAAc,GACrB,kBAAkB,CAAC,MAAM,CAAC,CAkE5B"}
@@ -1,44 +1,4 @@
1
1
  import React from 'react';
2
- import * as Babel from '@babel/standalone';
3
- /**
4
- * Define a template programmatically for use in serverless environments
5
- *
6
- * @example
7
- * ```typescript
8
- * const ogImage = defineTemplate({
9
- * name: 'og-image',
10
- * type: 'image',
11
- * size: { width: 1200, height: 630 },
12
- * render: ({ tw, title, description }) => (
13
- * <div style={tw('flex flex-col w-full h-full bg-white p-12')}>
14
- * <h1 style={tw('text-6xl font-bold')}>{title}</h1>
15
- * <p style={tw('text-2xl text-gray-600')}>{description}</p>
16
- * </div>
17
- * )
18
- * });
19
- * ```
20
- */
21
- export function defineTemplate(definition) {
22
- // Validate definition
23
- if (!definition.name) {
24
- throw new Error('Template definition must have a name');
25
- }
26
- if (!definition.size || !definition.size.width || !definition.size.height) {
27
- throw new Error('Template definition must have a size with width and height');
28
- }
29
- if (!definition.render || typeof definition.render !== 'function') {
30
- throw new Error('Template definition must have a render function');
31
- }
32
- // Set defaults
33
- const type = definition.type || 'image';
34
- if (type === 'video' && !definition.video) {
35
- throw new Error('Video templates must have video metadata (fps, duration)');
36
- }
37
- return {
38
- ...definition,
39
- type,
40
- };
41
- }
42
2
  /**
43
3
  * Convert a template definition to template metadata
44
4
  * @internal
@@ -56,93 +16,91 @@ export function templateToMeta(template) {
56
16
  /**
57
17
  * Define a template from an imported template file
58
18
  *
59
- * Allows reusing file-based templates from _loopwind/templates/ in the SDK
19
+ * The standard way to create templates in loopwind. Import a template file
20
+ * and pass it to defineTemplate() with optional config overrides.
60
21
  *
61
22
  * @example
62
23
  * ```typescript
63
- * import * as videoIntro from './_loopwind/templates/video-intro/template';
24
+ * import * as ogImage from './_loopwind/templates/og-image/template';
64
25
  *
65
- * const template = defineTemplateFromFile(videoIntro, {
26
+ * const template = defineTemplate(ogImage, {
66
27
  * config: {
67
28
  * colors: { primary: '#ff0000' }
68
29
  * }
69
30
  * });
70
31
  *
71
- * const mp4 = await renderVideo(template, { version: '2.0.0' });
32
+ * const png = await renderImage(template, { title: 'Hello' });
72
33
  * ```
73
34
  */
74
- export function defineTemplateFromFile(templateModule, options = {}) {
35
+ export function defineTemplate(templateModule, options = {}) {
75
36
  const { meta, default: render } = templateModule;
76
- return defineTemplate({
37
+ // Validate meta
38
+ if (!meta.name) {
39
+ throw new Error('Template meta must have a name');
40
+ }
41
+ if (!meta.size || !meta.size.width || !meta.size.height) {
42
+ throw new Error('Template meta must have a size with width and height');
43
+ }
44
+ const type = meta.type || 'image';
45
+ if (type === 'video' && !meta.video) {
46
+ throw new Error('Video templates must have video metadata (fps, duration)');
47
+ }
48
+ return {
77
49
  name: meta.name,
78
- type: meta.type,
50
+ type: type,
79
51
  size: meta.size,
80
52
  video: meta.video,
81
53
  config: options.config,
82
54
  render,
83
- });
55
+ };
84
56
  }
85
57
  /**
86
58
  * Define a template from a source code string
87
59
  *
60
+ * **IMPORTANT:** This function expects **pre-compiled JavaScript** with React.createElement
61
+ * calls. If you have JSX source code, compile it first using `compileTemplate()` from
62
+ * 'loopwind/sdk/compiler'.
63
+ *
88
64
  * **WARNING:** This function evaluates code strings using `new Function()`.
89
65
  * Only use with **trusted** template sources. Never use with user input from
90
66
  * untrusted sources as it can execute arbitrary code.
91
67
  *
92
- * Perfect for:
93
- * - Templates stored in a database (admin-created only)
94
- * - Templates from a trusted CMS
95
- * - Code editor features where templates are validated
68
+ * **Recommended workflow:**
69
+ * 1. In your admin panel: Use `compileTemplate()` to transform JSX → JavaScript
70
+ * 2. Store the compiled JavaScript in your database
71
+ * 3. In production: Load compiled code and pass to `defineTemplateFromSource()`
72
+ *
73
+ * This approach keeps @babel/standalone out of your production bundle!
96
74
  *
97
75
  * @example
98
76
  * ```typescript
99
- * const templateSource = `
100
- * export const meta = {
101
- * name: 'user-template',
102
- * type: 'image',
103
- * size: { width: 1200, height: 630 }
104
- * };
77
+ * // Admin panel: Compile and save
78
+ * import { compileTemplate } from 'loopwind/sdk/compiler';
105
79
  *
106
- * export default ({ tw, title }) => {
107
- * const h = React.createElement;
108
- * return h('div', { style: tw('flex w-full h-full bg-blue-500 p-12') },
109
- * h('h1', { style: tw('text-6xl font-bold text-white') }, title)
110
- * );
111
- * };
112
- * `;
80
+ * const jsxCode = getUserCodeFromEditor(); // Contains JSX
81
+ * const compiled = compileTemplate(jsxCode);
82
+ * await db.templates.save({ code: compiled });
83
+ * ```
113
84
  *
114
- * const template = defineTemplateFromSource(templateSource, {
85
+ * @example
86
+ * ```typescript
87
+ * // Production API: Load and render
88
+ * import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
89
+ *
90
+ * const template = await db.templates.findById(id);
91
+ * const templateDef = defineTemplateFromSource(template.code, {
115
92
  * config: { colors: { primary: '#3b82f6' } }
116
93
  * });
117
94
  *
118
- * const png = await renderImage(template, { title: 'Hello' });
95
+ * const png = await renderImage(templateDef, { title: 'Hello' });
119
96
  * ```
120
97
  */
121
- /**
122
- * Transform JSX to JavaScript using Babel
123
- * @internal
124
- */
125
- function transformJSX(code) {
126
- try {
127
- const result = Babel.transform(code, {
128
- presets: ['react'],
129
- filename: 'template.tsx',
130
- });
131
- return result.code || code;
132
- }
133
- catch (error) {
134
- // If Babel fails, return original code (might be plain JS without JSX)
135
- return code;
136
- }
137
- }
138
98
  export function defineTemplateFromSource(sourceCode, options = {}) {
139
99
  try {
140
- // Transform JSX to React.createElement calls
141
- let processedSource = transformJSX(sourceCode);
142
100
  // Strip export keywords to make it work in function context
143
101
  // Convert "export const meta = ..." to "const meta = ..."
144
102
  // Convert "export default ..." to "const defaultExport = ..."
145
- processedSource = processedSource
103
+ let processedSource = sourceCode
146
104
  .replace(/export\s+const\s+meta\s*=/g, 'const meta =')
147
105
  .replace(/export\s+default\s+/g, 'const defaultExport = ');
148
106
  // Create a module evaluation function
@@ -160,7 +118,7 @@ export function defineTemplateFromSource(sourceCode, options = {}) {
160
118
  if (!templateModule.default || typeof templateModule.default !== 'function') {
161
119
  throw new Error('Template source must have a default export function');
162
120
  }
163
- return defineTemplateFromFile(templateModule, options);
121
+ return defineTemplate(templateModule, options);
164
122
  }
165
123
  catch (error) {
166
124
  throw new Error(`Failed to parse template source: ${error instanceof Error ? error.message : String(error)}`);
@@ -259,13 +217,15 @@ export function defineTemplateFromSchema(schema) {
259
217
  return h(React.Fragment, null, elements.map((el, i) => buildElement(el, i)));
260
218
  }
261
219
  `)(React);
262
- return defineTemplate({
220
+ // Create a template module from the schema
221
+ const meta = {
263
222
  name: schema.name,
264
- type: schema.type,
223
+ description: schema.name,
224
+ type: schema.type || 'image',
265
225
  size: schema.size,
266
226
  video: schema.video,
267
- config: schema.config,
268
- render,
269
- });
227
+ props: {},
228
+ };
229
+ return defineTemplate({ meta, default: render }, { config: schema.config });
270
230
  }
271
231
  //# sourceMappingURL=template.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"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"}
@@ -6,13 +6,13 @@
6
6
  */
7
7
 
8
8
  import type { NextApiRequest, NextApiResponse } from 'next';
9
- import { defineTemplateFromFile, renderVideo } from 'loopwind/sdk';
9
+ import { defineTemplate, renderVideo } from 'loopwind/sdk';
10
10
 
11
11
  // Import your template file (Next.js/bundler handles TSX automatically)
12
12
  import * as videoIntroTemplate from '../../_loopwind/templates/video-intro/template';
13
13
 
14
14
  // Create SDK template from file with custom config
15
- const template = defineTemplateFromFile(videoIntroTemplate, {
15
+ const template = defineTemplate(videoIntroTemplate, {
16
16
  config: {
17
17
  colors: {
18
18
  primary: '#ff0000', // Override gradient colors