loopwind 0.18.0 → 0.19.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/REGISTRY_SETUP.md +1 -55
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +0 -3
- package/dist/commands/add.js.map +1 -1
- package/dist/lib/installer.d.ts +0 -8
- package/dist/lib/installer.d.ts.map +1 -1
- package/dist/lib/installer.js +1 -48
- package/dist/lib/installer.js.map +1 -1
- package/dist/sdk/compiler.d.ts +94 -0
- package/dist/sdk/compiler.d.ts.map +1 -0
- package/dist/sdk/compiler.js +122 -0
- package/dist/sdk/compiler.js.map +1 -0
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/template.d.ts +30 -43
- package/dist/sdk/template.d.ts.map +1 -1
- package/dist/sdk/template.js +52 -73
- package/dist/sdk/template.js.map +1 -1
- package/examples/template-compiler-workflow.ts +251 -0
- package/output/sdk-static.jpg +0 -0
- package/package.json +7 -1
- package/test-jsx-support.mjs +146 -0
- package/test-sdk-source-config.mjs +427 -0
- package/test-templates/config-test.mjs +17 -0
- package/website/astro.config.mjs +10 -0
- package/website/dist/.gitkeep +1 -0
- package/website/dist/_worker.js/index.js +1 -1
- package/website/dist/_worker.js/{manifest_BAAoOzaU.mjs → manifest_CT_D-YDe.mjs} +1 -1
- package/website/dist/llm.txt +1 -1
- package/website/dist/sdk/index.html +405 -102
- package/website/dist/sitemap.xml +12 -12
- package/website/package-lock.json +1077 -17
- package/website/package.json +5 -1
- package/website/public/.gitkeep +1 -0
- package/website/deploy.sh +0 -19
- package/website/public/.assetsignore +0 -3
- package/website/wrangler.toml +0 -12
package/dist/sdk/template.js
CHANGED
|
@@ -1,43 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* Define a template programmatically for use in serverless environments
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```typescript
|
|
7
|
-
* const ogImage = defineTemplate({
|
|
8
|
-
* name: 'og-image',
|
|
9
|
-
* type: 'image',
|
|
10
|
-
* size: { width: 1200, height: 630 },
|
|
11
|
-
* render: ({ tw, title, description }) => (
|
|
12
|
-
* <div style={tw('flex flex-col w-full h-full bg-white p-12')}>
|
|
13
|
-
* <h1 style={tw('text-6xl font-bold')}>{title}</h1>
|
|
14
|
-
* <p style={tw('text-2xl text-gray-600')}>{description}</p>
|
|
15
|
-
* </div>
|
|
16
|
-
* )
|
|
17
|
-
* });
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export function defineTemplate(definition) {
|
|
21
|
-
// Validate definition
|
|
22
|
-
if (!definition.name) {
|
|
23
|
-
throw new Error('Template definition must have a name');
|
|
24
|
-
}
|
|
25
|
-
if (!definition.size || !definition.size.width || !definition.size.height) {
|
|
26
|
-
throw new Error('Template definition must have a size with width and height');
|
|
27
|
-
}
|
|
28
|
-
if (!definition.render || typeof definition.render !== 'function') {
|
|
29
|
-
throw new Error('Template definition must have a render function');
|
|
30
|
-
}
|
|
31
|
-
// Set defaults
|
|
32
|
-
const type = definition.type || 'image';
|
|
33
|
-
if (type === 'video' && !definition.video) {
|
|
34
|
-
throw new Error('Video templates must have video metadata (fps, duration)');
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
...definition,
|
|
38
|
-
type,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
2
|
/**
|
|
42
3
|
* Convert a template definition to template metadata
|
|
43
4
|
* @internal
|
|
@@ -55,66 +16,83 @@ export function templateToMeta(template) {
|
|
|
55
16
|
/**
|
|
56
17
|
* Define a template from an imported template file
|
|
57
18
|
*
|
|
58
|
-
*
|
|
19
|
+
* The standard way to create templates in loopwind. Import a template file
|
|
20
|
+
* and pass it to defineTemplate() with optional config overrides.
|
|
59
21
|
*
|
|
60
22
|
* @example
|
|
61
23
|
* ```typescript
|
|
62
|
-
* import * as
|
|
24
|
+
* import * as ogImage from './_loopwind/templates/og-image/template';
|
|
63
25
|
*
|
|
64
|
-
* const template =
|
|
26
|
+
* const template = defineTemplate(ogImage, {
|
|
65
27
|
* config: {
|
|
66
28
|
* colors: { primary: '#ff0000' }
|
|
67
29
|
* }
|
|
68
30
|
* });
|
|
69
31
|
*
|
|
70
|
-
* const
|
|
32
|
+
* const png = await renderImage(template, { title: 'Hello' });
|
|
71
33
|
* ```
|
|
72
34
|
*/
|
|
73
|
-
export function
|
|
35
|
+
export function defineTemplate(templateModule, options = {}) {
|
|
74
36
|
const { meta, default: render } = templateModule;
|
|
75
|
-
|
|
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 {
|
|
76
49
|
name: meta.name,
|
|
77
|
-
type:
|
|
50
|
+
type: type,
|
|
78
51
|
size: meta.size,
|
|
79
52
|
video: meta.video,
|
|
80
53
|
config: options.config,
|
|
81
54
|
render,
|
|
82
|
-
}
|
|
55
|
+
};
|
|
83
56
|
}
|
|
84
57
|
/**
|
|
85
58
|
* Define a template from a source code string
|
|
86
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
|
+
*
|
|
87
64
|
* **WARNING:** This function evaluates code strings using `new Function()`.
|
|
88
65
|
* Only use with **trusted** template sources. Never use with user input from
|
|
89
66
|
* untrusted sources as it can execute arbitrary code.
|
|
90
67
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
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!
|
|
95
74
|
*
|
|
96
75
|
* @example
|
|
97
76
|
* ```typescript
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* name: 'user-template',
|
|
101
|
-
* type: 'image',
|
|
102
|
-
* size: { width: 1200, height: 630 }
|
|
103
|
-
* };
|
|
77
|
+
* // Admin panel: Compile and save
|
|
78
|
+
* import { compileTemplate } from 'loopwind/sdk/compiler';
|
|
104
79
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
80
|
+
* const jsxCode = getUserCodeFromEditor(); // Contains JSX
|
|
81
|
+
* const compiled = compileTemplate(jsxCode);
|
|
82
|
+
* await db.templates.save({ code: compiled });
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // Production API: Load and render
|
|
88
|
+
* import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
|
|
112
89
|
*
|
|
113
|
-
* const template =
|
|
90
|
+
* const template = await db.templates.findById(id);
|
|
91
|
+
* const templateDef = defineTemplateFromSource(template.code, {
|
|
114
92
|
* config: { colors: { primary: '#3b82f6' } }
|
|
115
93
|
* });
|
|
116
94
|
*
|
|
117
|
-
* const png = await renderImage(
|
|
95
|
+
* const png = await renderImage(templateDef, { title: 'Hello' });
|
|
118
96
|
* ```
|
|
119
97
|
*/
|
|
120
98
|
export function defineTemplateFromSource(sourceCode, options = {}) {
|
|
@@ -129,7 +107,6 @@ export function defineTemplateFromSource(sourceCode, options = {}) {
|
|
|
129
107
|
// This executes the source code and returns the exports
|
|
130
108
|
const moduleFactory = new Function('React', `
|
|
131
109
|
'use strict';
|
|
132
|
-
const h = React.createElement;
|
|
133
110
|
${processedSource}
|
|
134
111
|
return { meta, default: defaultExport };
|
|
135
112
|
`);
|
|
@@ -141,7 +118,7 @@ export function defineTemplateFromSource(sourceCode, options = {}) {
|
|
|
141
118
|
if (!templateModule.default || typeof templateModule.default !== 'function') {
|
|
142
119
|
throw new Error('Template source must have a default export function');
|
|
143
120
|
}
|
|
144
|
-
return
|
|
121
|
+
return defineTemplate(templateModule, options);
|
|
145
122
|
}
|
|
146
123
|
catch (error) {
|
|
147
124
|
throw new Error(`Failed to parse template source: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -240,13 +217,15 @@ export function defineTemplateFromSchema(schema) {
|
|
|
240
217
|
return h(React.Fragment, null, elements.map((el, i) => buildElement(el, i)));
|
|
241
218
|
}
|
|
242
219
|
`)(React);
|
|
243
|
-
|
|
220
|
+
// Create a template module from the schema
|
|
221
|
+
const meta = {
|
|
244
222
|
name: schema.name,
|
|
245
|
-
|
|
223
|
+
description: schema.name,
|
|
224
|
+
type: schema.type || 'image',
|
|
246
225
|
size: schema.size,
|
|
247
226
|
video: schema.video,
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
227
|
+
props: {},
|
|
228
|
+
};
|
|
229
|
+
return defineTemplate({ meta, default: render }, { config: schema.config });
|
|
251
230
|
}
|
|
252
231
|
//# sourceMappingURL=template.js.map
|
package/dist/sdk/template.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/sdk/template.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAA4B;IACzD,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,WAAW,EAAE,QAAQ,CAAC,IAAI,EAAE,4CAA4C;QACxE,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,OAAO;QAC9B,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,KAAK,EAAE,EAAE,EAAE,0CAA0C;KACtD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,cAAc,CAC5B,cAA+F,EAC/F,UAAoC,EAAE;IAEtC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC;IAEjD,gBAAgB;IAChB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC;IAElC,IAAI,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAyB;QAC/B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,wBAAwB,CACtC,UAAkB,EAClB,UAAoC,EAAE;IAEtC,IAAI,CAAC;QACH,4DAA4D;QAC5D,0DAA0D;QAC1D,8DAA8D;QAC9D,IAAI,eAAe,GAAG,UAAU;aAC7B,OAAO,CAAC,4BAA4B,EAAE,cAAc,CAAC;aACrD,OAAO,CAAC,sBAAsB,EAAE,wBAAwB,CAAC,CAAC;QAE7D,sCAAsC;QACtC,wDAAwD;QACxD,MAAM,aAAa,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE;;QAExC,eAAe;;KAElB,CAAC,CAAC;QAEH,0CAA0C;QAC1C,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QAE5C,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI,OAAO,cAAc,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YAC5E,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,cAAc,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,oCAAoC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChH,CAAC;AACH,CAAC;AA0BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAAsB;IAEtB,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,+DAA+D;IAC/D,gFAAgF;IAChF,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAErD,qDAAqD;IACrD,wEAAwE;IACxE,gDAAgD;IAChD,MAAM,MAAM,GAAmD,IAAI,QAAQ,CAAC,OAAO,EAAE;;;yBAG9D,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsClC,CAAC,CAAC,KAAK,CAAQ,CAAC;IAEjB,2CAA2C;IAC3C,MAAM,IAAI,GAAiB;QACzB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,WAAW,EAAE,MAAM,CAAC,IAAI;QACxB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,OAAO;QAC5B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,EAAE;KACV,CAAC;IAEF,OAAO,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Template Compiler Workflow
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the recommended production workflow for JSX templates:
|
|
5
|
+
*
|
|
6
|
+
* 1. ADMIN PANEL: Use compileTemplate() to transform JSX → JavaScript
|
|
7
|
+
* 2. DATABASE: Store the compiled JavaScript
|
|
8
|
+
* 3. PRODUCTION API: Use defineTemplateFromSource() to render
|
|
9
|
+
*
|
|
10
|
+
* Benefits:
|
|
11
|
+
* - JSX developer experience in admin panel
|
|
12
|
+
* - No @babel/standalone in production (36 MB smaller!)
|
|
13
|
+
* - Faster cold starts in serverless functions
|
|
14
|
+
* - Syntax errors caught at save time, not render time
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { compileTemplate, containsJSX } from 'loopwind/sdk/compiler';
|
|
18
|
+
import { defineTemplateFromSource, renderImage } from 'loopwind/sdk';
|
|
19
|
+
import fs from 'fs/promises';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// PART 1: Admin Panel - Compile and Save
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Simulates an admin panel API where users edit templates in a code editor
|
|
27
|
+
*/
|
|
28
|
+
async function adminPanelSaveTemplate(templateId: string, userCode: string) {
|
|
29
|
+
console.log('📝 Admin Panel: User saved template\n');
|
|
30
|
+
console.log('User-provided JSX:');
|
|
31
|
+
console.log(userCode.substring(0, 200) + '...\n');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Check if the code contains JSX
|
|
35
|
+
if (containsJSX(userCode)) {
|
|
36
|
+
console.log('✓ Detected JSX syntax, compiling...');
|
|
37
|
+
|
|
38
|
+
// Compile JSX to JavaScript
|
|
39
|
+
const compiled = compileTemplate(userCode);
|
|
40
|
+
|
|
41
|
+
console.log('✓ Compilation successful!');
|
|
42
|
+
console.log(' Original size:', userCode.length, 'bytes');
|
|
43
|
+
console.log(' Compiled size:', compiled.length, 'bytes\n');
|
|
44
|
+
|
|
45
|
+
// In a real app, you would save to a database:
|
|
46
|
+
// await db.templates.update(templateId, {
|
|
47
|
+
// sourceCode: compiled,
|
|
48
|
+
// updatedAt: new Date()
|
|
49
|
+
// });
|
|
50
|
+
|
|
51
|
+
// For this example, save to a file
|
|
52
|
+
await fs.writeFile(`template-${templateId}-compiled.js`, compiled);
|
|
53
|
+
console.log('✓ Saved compiled template to database\n');
|
|
54
|
+
|
|
55
|
+
return { success: true, compiled };
|
|
56
|
+
} else {
|
|
57
|
+
console.log('ℹ No JSX detected, saving as-is\n');
|
|
58
|
+
return { success: true, compiled: userCode };
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('✗ Compilation failed:', (error as Error).message);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: (error as Error).message,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// PART 2: Production API - Load and Render
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Simulates a production API endpoint that renders templates
|
|
75
|
+
* This runs in Vercel, AWS Lambda, or any serverless function
|
|
76
|
+
*/
|
|
77
|
+
async function productionApiRenderTemplate(
|
|
78
|
+
templateId: string,
|
|
79
|
+
props: Record<string, any>
|
|
80
|
+
) {
|
|
81
|
+
console.log('🚀 Production API: Rendering template\n');
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Load pre-compiled template from database
|
|
85
|
+
// In a real app:
|
|
86
|
+
// const template = await db.templates.findById(templateId);
|
|
87
|
+
// const compiledCode = template.sourceCode;
|
|
88
|
+
|
|
89
|
+
// For this example, load from file
|
|
90
|
+
const compiledCode = await fs.readFile(
|
|
91
|
+
`template-${templateId}-compiled.js`,
|
|
92
|
+
'utf-8'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
console.log('✓ Loaded pre-compiled template from database');
|
|
96
|
+
console.log(' Size:', compiledCode.length, 'bytes');
|
|
97
|
+
console.log(' No Babel needed! 🎉\n');
|
|
98
|
+
|
|
99
|
+
// Define template from pre-compiled JavaScript
|
|
100
|
+
// This is FAST - no JSX transformation at runtime
|
|
101
|
+
const templateDef = defineTemplateFromSource(compiledCode, {
|
|
102
|
+
config: {
|
|
103
|
+
colors: {
|
|
104
|
+
primary: '#3b82f6',
|
|
105
|
+
secondary: '#8b5cf6',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('✓ Template loaded');
|
|
111
|
+
console.log(' Name:', templateDef.name);
|
|
112
|
+
console.log(' Type:', templateDef.type);
|
|
113
|
+
console.log(' Size:', templateDef.size.width, 'x', templateDef.size.height, '\n');
|
|
114
|
+
|
|
115
|
+
// Render the image
|
|
116
|
+
const png = await renderImage(templateDef, props);
|
|
117
|
+
|
|
118
|
+
console.log('✓ Image rendered');
|
|
119
|
+
console.log(' PNG size:', png.length, 'bytes\n');
|
|
120
|
+
|
|
121
|
+
return png;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('✗ Rendering failed:', (error as Error).message);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// DEMO: Full Workflow
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
async function demo() {
|
|
133
|
+
console.log('='.repeat(70));
|
|
134
|
+
console.log('🎨 Template Compiler Workflow Demo');
|
|
135
|
+
console.log('='.repeat(70) + '\n');
|
|
136
|
+
|
|
137
|
+
// Example JSX template that a user might write in a code editor
|
|
138
|
+
const userJSX = `
|
|
139
|
+
export const meta = {
|
|
140
|
+
name: 'social-card',
|
|
141
|
+
type: 'image',
|
|
142
|
+
size: { width: 1200, height: 630 }
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default ({ tw, title, description, author }) => (
|
|
146
|
+
<div style={tw('flex flex-col justify-between w-full h-full bg-gradient-to-br from-primary to-secondary p-12')}>
|
|
147
|
+
<div style={tw('flex flex-col')}>
|
|
148
|
+
<h1 style={tw('text-6xl font-bold text-white mb-4')}>
|
|
149
|
+
{title}
|
|
150
|
+
</h1>
|
|
151
|
+
<p style={tw('text-2xl text-white/90')}>
|
|
152
|
+
{description}
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
<div style={tw('flex items-center gap-4')}>
|
|
156
|
+
<div style={tw('w-16 h-16 rounded-full bg-white/20')} />
|
|
157
|
+
<span style={tw('text-xl text-white/80')}>{author}</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const templateId = 'card-123';
|
|
164
|
+
|
|
165
|
+
// STEP 1: Admin panel compiles and saves
|
|
166
|
+
console.log('STEP 1: Admin Panel Workflow');
|
|
167
|
+
console.log('-'.repeat(70) + '\n');
|
|
168
|
+
await adminPanelSaveTemplate(templateId, userJSX);
|
|
169
|
+
|
|
170
|
+
// STEP 2: Production API loads and renders
|
|
171
|
+
console.log('STEP 2: Production API Workflow');
|
|
172
|
+
console.log('-'.repeat(70) + '\n');
|
|
173
|
+
const png = await productionApiRenderTemplate(templateId, {
|
|
174
|
+
title: 'Building with loopwind',
|
|
175
|
+
description: 'Generate images and videos programmatically',
|
|
176
|
+
author: 'Tommy Vedvik',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Save the final result
|
|
180
|
+
await fs.writeFile('social-card-demo.png', png);
|
|
181
|
+
console.log('✅ Saved final image to social-card-demo.png\n');
|
|
182
|
+
|
|
183
|
+
// Cleanup
|
|
184
|
+
await fs.unlink(`template-${templateId}-compiled.js`);
|
|
185
|
+
|
|
186
|
+
console.log('='.repeat(70));
|
|
187
|
+
console.log('📊 Workflow Summary');
|
|
188
|
+
console.log('='.repeat(70));
|
|
189
|
+
console.log('\n✅ Benefits of this approach:\n');
|
|
190
|
+
console.log('1. Users write natural JSX syntax in admin panel');
|
|
191
|
+
console.log('2. JSX compiled once at save time (not every render)');
|
|
192
|
+
console.log('3. Compiled templates stored in database');
|
|
193
|
+
console.log('4. Production bundle 36 MB smaller (no @babel/standalone)');
|
|
194
|
+
console.log('5. Faster cold starts in serverless functions');
|
|
195
|
+
console.log('6. Syntax errors caught early (at save, not production)\n');
|
|
196
|
+
|
|
197
|
+
console.log('🔧 For Vercel/Lambda deployments:\n');
|
|
198
|
+
console.log('- Admin panel imports: loopwind/sdk/compiler');
|
|
199
|
+
console.log('- Production API imports: loopwind/sdk');
|
|
200
|
+
console.log('- Result: Only admin panel includes Babel\n');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Example Integration Code
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Example: Next.js Admin Panel API Route
|
|
209
|
+
*/
|
|
210
|
+
async function exampleNextJSAdminRoute(req: any, res: any) {
|
|
211
|
+
const { templateId, code } = req.body;
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
// Compile JSX
|
|
215
|
+
const compiled = compileTemplate(code);
|
|
216
|
+
|
|
217
|
+
// Save to database
|
|
218
|
+
// await db.templates.update(templateId, { sourceCode: compiled });
|
|
219
|
+
|
|
220
|
+
res.json({ success: true });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
res.status(400).json({
|
|
223
|
+
error: 'Compilation failed',
|
|
224
|
+
message: (error as Error).message,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Example: Next.js Production API Route
|
|
231
|
+
*/
|
|
232
|
+
async function exampleNextJSProductionRoute(req: any, res: any) {
|
|
233
|
+
const { templateId, props } = req.body;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
// Load pre-compiled template
|
|
237
|
+
// const template = await db.templates.findById(templateId);
|
|
238
|
+
// const templateDef = defineTemplateFromSource(template.sourceCode);
|
|
239
|
+
|
|
240
|
+
// Render
|
|
241
|
+
// const png = await renderImage(templateDef, props);
|
|
242
|
+
|
|
243
|
+
// res.setHeader('Content-Type', 'image/png');
|
|
244
|
+
// res.send(png);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
res.status(500).json({ error: 'Rendering failed' });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Run the demo
|
|
251
|
+
demo().catch(console.error);
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopwind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "A CLI tool for AI code agents and developers for generating images and videos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"./sdk": {
|
|
16
16
|
"types": "./dist/sdk/index.d.ts",
|
|
17
17
|
"default": "./dist/sdk/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./sdk/compiler": {
|
|
20
|
+
"types": "./dist/sdk/compiler.d.ts",
|
|
21
|
+
"default": "./dist/sdk/compiler.js"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"scripts": {
|
|
@@ -74,6 +78,8 @@
|
|
|
74
78
|
"vite": "^7.2.2"
|
|
75
79
|
},
|
|
76
80
|
"devDependencies": {
|
|
81
|
+
"@babel/standalone": "^7.28.5",
|
|
82
|
+
"@types/babel__standalone": "^7.1.9",
|
|
77
83
|
"@types/chalk-animation": "^1.6.3",
|
|
78
84
|
"@types/express": "^5.0.5",
|
|
79
85
|
"@types/gradient-string": "^1.1.6",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test JSX support with compileTemplate + defineTemplateFromSource
|
|
3
|
+
*
|
|
4
|
+
* This test demonstrates the recommended workflow:
|
|
5
|
+
* 1. compileTemplate() - transforms JSX to JavaScript (admin/build time)
|
|
6
|
+
* 2. defineTemplateFromSource() - loads pre-compiled JavaScript (production)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { defineTemplateFromSource, renderImage } from './dist/sdk/index.js';
|
|
10
|
+
import { compileTemplate } from './dist/sdk/compiler.js';
|
|
11
|
+
|
|
12
|
+
console.log('Testing JSX Support with Pre-compilation\n');
|
|
13
|
+
|
|
14
|
+
// Test 1: Simple JSX template
|
|
15
|
+
const simpleJSX = `
|
|
16
|
+
export const meta = {
|
|
17
|
+
name: 'jsx-test',
|
|
18
|
+
type: 'image',
|
|
19
|
+
size: { width: 400, height: 200 }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default ({ tw, title }) => (
|
|
23
|
+
<div style={tw('flex items-center justify-center w-full h-full bg-blue-500')}>
|
|
24
|
+
<h1 style={tw('text-4xl font-bold text-white')}>{title}</h1>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Step 1: Compile JSX to JavaScript (admin/build time)
|
|
31
|
+
const compiledJS = compileTemplate(simpleJSX);
|
|
32
|
+
|
|
33
|
+
// Step 2: Load compiled template (production runtime)
|
|
34
|
+
const template1 = defineTemplateFromSource(compiledJS);
|
|
35
|
+
const png1 = await renderImage(template1, { title: 'JSX Works!' });
|
|
36
|
+
console.log('✓ Simple JSX template works');
|
|
37
|
+
console.log(' PNG size:', png1.length, 'bytes\n');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('✗ Simple JSX template failed:', error.message);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test 2: Nested JSX
|
|
43
|
+
const nestedJSX = `
|
|
44
|
+
export const meta = {
|
|
45
|
+
name: 'nested-jsx',
|
|
46
|
+
size: { width: 600, height: 300 }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default ({ tw, title, subtitle, items }) => (
|
|
50
|
+
<div style={tw('flex flex-col w-full h-full bg-white p-8')}>
|
|
51
|
+
<h1 style={tw('text-5xl font-bold text-gray-900 mb-4')}>{title}</h1>
|
|
52
|
+
<p style={tw('text-2xl text-gray-600 mb-6')}>{subtitle}</p>
|
|
53
|
+
<div style={tw('flex gap-4')}>
|
|
54
|
+
{items.map((item, i) => (
|
|
55
|
+
<div style={tw('px-4 py-2 bg-blue-500 text-white rounded')}>
|
|
56
|
+
{item}
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Step 1: Compile JSX to JavaScript
|
|
66
|
+
const compiledJS2 = compileTemplate(nestedJSX);
|
|
67
|
+
|
|
68
|
+
// Step 2: Load compiled template
|
|
69
|
+
const template2 = defineTemplateFromSource(compiledJS2);
|
|
70
|
+
const png2 = await renderImage(template2, {
|
|
71
|
+
title: 'Nested JSX',
|
|
72
|
+
subtitle: 'With multiple levels',
|
|
73
|
+
items: ['One', 'Two', 'Three']
|
|
74
|
+
});
|
|
75
|
+
console.log('✓ Nested JSX template works');
|
|
76
|
+
console.log(' PNG size:', png2.length, 'bytes\n');
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('✗ Nested JSX template failed:', error.message);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Test 3: Self-closing tags
|
|
82
|
+
const selfClosing = `
|
|
83
|
+
export const meta = {
|
|
84
|
+
name: 'self-closing',
|
|
85
|
+
size: { width: 400, height: 200 }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default ({ tw }) => (
|
|
89
|
+
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-gray-100 gap-4')}>
|
|
90
|
+
<div style={tw('w-32 h-32 bg-red-500 rounded-full')} />
|
|
91
|
+
<div style={tw('w-32 h-32 bg-blue-500 rounded-lg')} />
|
|
92
|
+
<div style={tw('w-32 h-32 bg-green-500')} />
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Step 1: Compile JSX to JavaScript
|
|
99
|
+
const compiledJS3 = compileTemplate(selfClosing);
|
|
100
|
+
|
|
101
|
+
// Step 2: Load compiled template
|
|
102
|
+
const template3 = defineTemplateFromSource(compiledJS3);
|
|
103
|
+
const png3 = await renderImage(template3, {});
|
|
104
|
+
console.log('✓ Self-closing tags work');
|
|
105
|
+
console.log(' PNG size:', png3.length, 'bytes\n');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('✗ Self-closing tags failed:', error.message);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Test 4: Complex attributes
|
|
111
|
+
const complexAttrs = `
|
|
112
|
+
export const meta = {
|
|
113
|
+
name: 'complex-attrs',
|
|
114
|
+
size: { width: 500, height: 250 }
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export default ({ tw, title, count }) => (
|
|
118
|
+
<div style={tw('flex flex-col w-full h-full bg-gradient-to-br from-purple-600 to-pink-500 p-8')}>
|
|
119
|
+
<h1 style={tw('text-4xl font-bold text-white mb-4')}>{title}</h1>
|
|
120
|
+
<div style={tw('flex items-center gap-2')}>
|
|
121
|
+
<span style={tw('text-2xl text-white')}>Count:</span>
|
|
122
|
+
<span style={tw('text-3xl font-bold text-yellow-300')}>{count}</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Step 1: Compile JSX to JavaScript
|
|
130
|
+
const compiledJS4 = compileTemplate(complexAttrs);
|
|
131
|
+
|
|
132
|
+
// Step 2: Load compiled template
|
|
133
|
+
const template4 = defineTemplateFromSource(compiledJS4);
|
|
134
|
+
const png4 = await renderImage(template4, { title: 'Complex JSX', count: 42 });
|
|
135
|
+
console.log('✓ Complex attributes work');
|
|
136
|
+
console.log(' PNG size:', png4.length, 'bytes\n');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('✗ Complex attributes failed:', error.message);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('✅ All JSX tests completed!');
|
|
142
|
+
console.log('\nWorkflow Summary:');
|
|
143
|
+
console.log('1. compileTemplate() - Transform JSX → JS (admin panel)');
|
|
144
|
+
console.log('2. defineTemplateFromSource() - Load compiled JS (production)');
|
|
145
|
+
console.log('3. renderImage() - Render with props');
|
|
146
|
+
console.log('\nBenefit: No @babel/standalone in production! 🎉');
|