selkie 1.0.0 → 1.2.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 +51 -5
- package/index.js +79 -22
- package/package.json +26 -7
- package/package_details.js +105 -5
- package/templates/build.js +268 -0
- package/{templates.js → templates/default.js} +3 -2
- package/templates/glsl.js +251 -0
- package/templates/ml5-hand.js +367 -0
- package/templates/pressure.js +222 -0
- package/templates/svg.js +142 -0
- package/templates/templates.js +18 -0
- package/templates/three.js +149 -0
package/README.md
CHANGED
|
@@ -1,19 +1,65 @@
|
|
|
1
|
-
# Selkie
|
|
1
|
+
# Selkie
|
|
2
2
|
|
|
3
3
|
In Celtic mythology, selkies are beings that can transform from seals into humans by shedding their seal skins. They often feature in stories of love and longing, where a selkie's human form is hidden away or discovered by a human.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A CLI tool for scaffolding generative art projects. Quickly create projects with p5.js, Paper.js, Three.js, WebGL/GLSL shaders, or ML5.js hand tracking.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install selkie
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Create a new project with one of the available templates:
|
|
13
16
|
|
|
14
|
-
In order to create a new project, use the following after it has been installed.
|
|
15
17
|
```bash
|
|
16
|
-
selkie -t default
|
|
18
|
+
selkie -t default # p5.js canvas project
|
|
19
|
+
selkie -t svg # Paper.js vector/SVG project
|
|
20
|
+
selkie -t three # Three.js 3D project
|
|
21
|
+
selkie -t glsl # WebGL shader project
|
|
22
|
+
selkie -t ml5-hand # ML5.js hand tracking project
|
|
23
|
+
selkie -t pressure # p5.js pressure drawing (iPad + Apple Pencil)
|
|
17
24
|
```
|
|
18
25
|
|
|
26
|
+
You'll be prompted for a folder name, then the project will be created with all dependencies installed.
|
|
27
|
+
|
|
28
|
+
## Working on Your Sketch
|
|
29
|
+
|
|
30
|
+
Each generated project includes a dev server:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd your-project
|
|
34
|
+
npm start # Opens in browser with live reload
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Edit `scripts/sketch.js` to create your generative art. Press 's' to export PNG/SVG (depending on template).
|
|
38
|
+
|
|
39
|
+
## Building a Single-File Bundle
|
|
40
|
+
|
|
41
|
+
When you're ready to share your sketch, build it into a single HTML file that works offline on any device:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run build
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This creates `dist/bundle.html` - a minified, self-contained file you can:
|
|
48
|
+
- Open directly in any browser (no server needed)
|
|
49
|
+
- Share via email, USB, or any file transfer
|
|
50
|
+
- Upload to any static hosting
|
|
51
|
+
|
|
52
|
+
**Note:** The ML5 hand tracking template still requires internet for AI models.
|
|
53
|
+
|
|
54
|
+
## Templates
|
|
55
|
+
|
|
56
|
+
| Template | Library | Output | Notes |
|
|
57
|
+
|----------|---------|--------|-------|
|
|
58
|
+
| `default` | p5.js | Canvas/PNG | Classic creative coding |
|
|
59
|
+
| `svg` | Paper.js | SVG vectors | Perfect for plotters |
|
|
60
|
+
| `three` | Three.js | WebGL 3D | 3D graphics |
|
|
61
|
+
| `glsl` | Native WebGL | Shaders | Smallest bundle (~5KB) |
|
|
62
|
+
| `ml5-hand` | ML5.js | Hand tracking | Requires webcam + internet |
|
|
63
|
+
|
|
64
|
+
All templates include a seeded random number generator for deterministic/reproducible outputs.
|
|
19
65
|
|
package/index.js
CHANGED
|
@@ -6,17 +6,20 @@ import path from 'path';
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { exec, execSync } from 'child_process';
|
|
9
|
-
import { template as defaultTemplate } from './templates.js';
|
|
10
|
-
import { packageJsonContent } from './package_details.js';
|
|
9
|
+
import { template as defaultTemplate, svgTemplate, threeTemplate, glslTemplate, ml5HandTrackingTemplate, pressureTemplate, buildScriptContent } from './templates/templates.js';
|
|
10
|
+
import { packageJsonContent, svgPackageJsonContent, threePackageJsonContent, glslPackageJsonContent, ml5HandTrackingPackageJsonContent, pressurePackageJsonContent } from './package_details.js';
|
|
11
11
|
|
|
12
12
|
// Convert import.meta.url to a file path
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
|
|
16
|
+
// Read version from package.json so --version stays in sync with releases
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
18
|
+
|
|
16
19
|
// Initialize commander
|
|
17
20
|
const program = new Command();
|
|
18
21
|
program
|
|
19
|
-
.version(
|
|
22
|
+
.version(pkg.version)
|
|
20
23
|
.option('-t, --template <type>', 'specify the template type')
|
|
21
24
|
.parse(process.argv);
|
|
22
25
|
|
|
@@ -45,15 +48,41 @@ async function createProject() {
|
|
|
45
48
|
await fs.ensureDir(scriptsPath);
|
|
46
49
|
|
|
47
50
|
// Choose the template
|
|
48
|
-
let htmlContent, sketchContent, randomContent;
|
|
49
|
-
|
|
51
|
+
let htmlContent, sketchContent, randomContent, selectedPackageJson;
|
|
52
|
+
let templateType = options.template || 'default';
|
|
50
53
|
|
|
51
|
-
if (
|
|
54
|
+
if (templateType === 'default') {
|
|
52
55
|
htmlContent = defaultTemplate.htmlContent;
|
|
53
56
|
sketchContent = defaultTemplate.sketchContent;
|
|
54
57
|
randomContent = defaultTemplate.randomContent;
|
|
58
|
+
selectedPackageJson = packageJsonContent;
|
|
59
|
+
} else if (templateType === 'svg') {
|
|
60
|
+
htmlContent = svgTemplate.htmlContent;
|
|
61
|
+
sketchContent = svgTemplate.sketchContent;
|
|
62
|
+
randomContent = svgTemplate.randomContent;
|
|
63
|
+
selectedPackageJson = svgPackageJsonContent;
|
|
64
|
+
} else if (templateType === 'three') {
|
|
65
|
+
htmlContent = threeTemplate.htmlContent;
|
|
66
|
+
sketchContent = threeTemplate.sketchContent;
|
|
67
|
+
randomContent = threeTemplate.randomContent;
|
|
68
|
+
selectedPackageJson = threePackageJsonContent;
|
|
69
|
+
} else if (templateType === 'glsl') {
|
|
70
|
+
htmlContent = glslTemplate.htmlContent;
|
|
71
|
+
sketchContent = glslTemplate.sketchContent;
|
|
72
|
+
randomContent = glslTemplate.randomContent;
|
|
73
|
+
selectedPackageJson = glslPackageJsonContent;
|
|
74
|
+
} else if (templateType === 'ml5-hand') {
|
|
75
|
+
htmlContent = ml5HandTrackingTemplate.htmlContent;
|
|
76
|
+
sketchContent = ml5HandTrackingTemplate.sketchContent;
|
|
77
|
+
randomContent = ml5HandTrackingTemplate.randomContent;
|
|
78
|
+
selectedPackageJson = ml5HandTrackingPackageJsonContent;
|
|
79
|
+
} else if (templateType === 'pressure') {
|
|
80
|
+
htmlContent = pressureTemplate.htmlContent;
|
|
81
|
+
sketchContent = pressureTemplate.sketchContent;
|
|
82
|
+
randomContent = pressureTemplate.randomContent;
|
|
83
|
+
selectedPackageJson = pressurePackageJsonContent;
|
|
55
84
|
} else {
|
|
56
|
-
throw new Error('Unknown template type');
|
|
85
|
+
throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand, pressure');
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
// Create index.html
|
|
@@ -61,33 +90,61 @@ async function createProject() {
|
|
|
61
90
|
|
|
62
91
|
// Create package.json
|
|
63
92
|
const packageJsonPath = path.join(folderPath, 'package.json');
|
|
64
|
-
const packageJson =
|
|
93
|
+
const packageJson = selectedPackageJson.replace(/"project-name"/, `"${folderName}"`);
|
|
65
94
|
await fs.writeFile(packageJsonPath, packageJson.trim());
|
|
66
95
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
96
|
+
// Install dependencies
|
|
97
|
+
console.log('Installing dependencies...');
|
|
98
|
+
execSync('npm install -s', { cwd: folderPath, stdio: 'inherit' });
|
|
99
|
+
|
|
100
|
+
// Copy the appropriate library to the scripts folder
|
|
101
|
+
if (templateType === 'svg') {
|
|
102
|
+
// Copy paper.js for SVG template
|
|
103
|
+
const paperPath = path.join(__dirname, 'node_modules', 'paper', 'dist', 'paper-full.js');
|
|
104
|
+
if (await fs.pathExists(paperPath)) {
|
|
105
|
+
await fs.copyFile(paperPath, path.join(scriptsPath, 'paper-full.js'));
|
|
106
|
+
console.log('paper.js copied successfully.');
|
|
107
|
+
} else {
|
|
108
|
+
throw new Error(`paper-full.js not found at ${paperPath}`);
|
|
109
|
+
}
|
|
110
|
+
} else if (templateType === 'three') {
|
|
111
|
+
// Copy three.js module for Three.js template
|
|
112
|
+
const threePath = path.join(__dirname, 'node_modules', 'three', 'build', 'three.module.js');
|
|
113
|
+
if (await fs.pathExists(threePath)) {
|
|
114
|
+
await fs.copyFile(threePath, path.join(scriptsPath, 'three.module.js'));
|
|
115
|
+
console.log('three.js copied successfully.');
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error(`three.module.js not found at ${threePath}`);
|
|
118
|
+
}
|
|
119
|
+
} else if (templateType === 'glsl') {
|
|
120
|
+
// GLSL template uses native WebGL, no external library needed
|
|
121
|
+
console.log('GLSL template - using native WebGL (no library needed).');
|
|
122
|
+
} else if (templateType === 'ml5-hand') {
|
|
123
|
+
// ML5 template uses CDN, no local library needed
|
|
124
|
+
console.log('ML5 hand tracking template - using ml5.js from CDN.');
|
|
76
125
|
} else {
|
|
77
|
-
|
|
126
|
+
// Copy p5.js for default template
|
|
127
|
+
const p5Path = path.join(__dirname, 'node_modules', 'p5', 'lib', 'p5.min.js');
|
|
128
|
+
if (await fs.pathExists(p5Path)) {
|
|
129
|
+
await fs.copyFile(p5Path, path.join(scriptsPath, 'p5.js'));
|
|
130
|
+
console.log('p5.js copied successfully.');
|
|
131
|
+
} else {
|
|
132
|
+
throw new Error(`p5.min.js not found at ${p5Path}`);
|
|
133
|
+
}
|
|
78
134
|
}
|
|
79
135
|
|
|
80
136
|
// Create sketch.js
|
|
81
137
|
await fs.writeFile(path.join(scriptsPath, 'sketch.js'), sketchContent.trim());
|
|
82
|
-
|
|
83
138
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
139
|
+
// Create random.js
|
|
140
|
+
await fs.writeFile(path.join(scriptsPath, 'random.js'), randomContent.trim());
|
|
87
141
|
|
|
88
142
|
// Create index.html
|
|
89
143
|
await fs.writeFile(path.join(folderPath, 'index.html'), htmlContent.trim());
|
|
90
144
|
|
|
145
|
+
// Create build.js for single-file bundling
|
|
146
|
+
await fs.writeFile(path.join(folderPath, 'build.js'), buildScriptContent.trim());
|
|
147
|
+
console.log('build.js created - run "npm run build" to create a single-file bundle.');
|
|
91
148
|
|
|
92
149
|
console.log(`Project ${folderName} created successfully.`);
|
|
93
150
|
|
package/package.json
CHANGED
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "selkie",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "CLI for scaffolding generative art projects with p5.js, Paper.js, Three.js, GLSL, and ML5.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"selkie": "./index.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"templates/",
|
|
13
|
+
"package_details.js",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
10
16
|
"scripts": {
|
|
11
17
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
18
|
},
|
|
13
|
-
"keywords": [
|
|
19
|
+
"keywords": [
|
|
20
|
+
"generative-art",
|
|
21
|
+
"creative-coding",
|
|
22
|
+
"p5js",
|
|
23
|
+
"paperjs",
|
|
24
|
+
"threejs",
|
|
25
|
+
"glsl",
|
|
26
|
+
"webgl",
|
|
27
|
+
"scaffold",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
14
30
|
"author": "generatecoll",
|
|
15
31
|
"license": "ISC",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/generatecoll/selkie"
|
|
35
|
+
},
|
|
16
36
|
"dependencies": {
|
|
17
37
|
"commander": "^12.1.0",
|
|
18
38
|
"fs-extra": "^11.2.0",
|
|
19
39
|
"inquirer": "^9.2.22",
|
|
20
|
-
"p5": "^1.9.4"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"live-server": "^1.2.2"
|
|
40
|
+
"p5": "^1.9.4",
|
|
41
|
+
"paper": "^0.12.18",
|
|
42
|
+
"three": "^0.170.0"
|
|
24
43
|
}
|
|
25
44
|
}
|
package/package_details.js
CHANGED
|
@@ -5,16 +5,116 @@ export const packageJsonContent = `{
|
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
|
|
8
|
+
"start": "live-server",
|
|
9
|
+
"build": "node build.js"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
|
|
12
|
+
"p5": "^1.4.0"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
|
|
15
|
+
"live-server": "^1.2.1",
|
|
16
|
+
"terser": "^5.31.0"
|
|
15
17
|
},
|
|
16
18
|
"keywords": [],
|
|
17
19
|
"author": "",
|
|
18
20
|
"license": "ISC"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
}`;
|
|
22
|
+
|
|
23
|
+
export const pressurePackageJsonContent = `{
|
|
24
|
+
"name": "project-name",
|
|
25
|
+
"version": "1.0.0",
|
|
26
|
+
"description": "Pressure-sensitive p5.js drawing (iPad + Apple Pencil)",
|
|
27
|
+
"main": "index.js",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"start": "live-server",
|
|
30
|
+
"build": "node build.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"p5": "^1.4.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"live-server": "^1.2.1",
|
|
37
|
+
"terser": "^5.31.0"
|
|
38
|
+
},
|
|
39
|
+
"keywords": ["generative", "p5.js", "apple-pencil", "pressure", "drawing"],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "ISC"
|
|
42
|
+
}`;
|
|
43
|
+
|
|
44
|
+
export const svgPackageJsonContent = `{
|
|
45
|
+
"name": "project-name",
|
|
46
|
+
"version": "1.0.0",
|
|
47
|
+
"description": "Paper.js SVG generative art project",
|
|
48
|
+
"main": "index.js",
|
|
49
|
+
"scripts": {
|
|
50
|
+
"start": "live-server",
|
|
51
|
+
"build": "node build.js"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"paper": "^0.12.18"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"live-server": "^1.2.1",
|
|
58
|
+
"terser": "^5.31.0"
|
|
59
|
+
},
|
|
60
|
+
"keywords": ["generative", "svg", "paper.js"],
|
|
61
|
+
"author": "",
|
|
62
|
+
"license": "ISC"
|
|
63
|
+
}`;
|
|
64
|
+
|
|
65
|
+
export const threePackageJsonContent = `{
|
|
66
|
+
"name": "project-name",
|
|
67
|
+
"version": "1.0.0",
|
|
68
|
+
"description": "Three.js 3D generative art project",
|
|
69
|
+
"main": "index.js",
|
|
70
|
+
"scripts": {
|
|
71
|
+
"start": "live-server",
|
|
72
|
+
"build": "node build.js"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"three": "^0.170.0"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"live-server": "^1.2.1",
|
|
79
|
+
"terser": "^5.31.0"
|
|
80
|
+
},
|
|
81
|
+
"keywords": ["generative", "3d", "three.js", "webgl"],
|
|
82
|
+
"author": "",
|
|
83
|
+
"license": "ISC"
|
|
84
|
+
}`;
|
|
85
|
+
|
|
86
|
+
export const glslPackageJsonContent = `{
|
|
87
|
+
"name": "project-name",
|
|
88
|
+
"version": "1.0.0",
|
|
89
|
+
"description": "GLSL WebGL shader project",
|
|
90
|
+
"main": "index.js",
|
|
91
|
+
"scripts": {
|
|
92
|
+
"start": "live-server",
|
|
93
|
+
"build": "node build.js"
|
|
94
|
+
},
|
|
95
|
+
"devDependencies": {
|
|
96
|
+
"live-server": "^1.2.1",
|
|
97
|
+
"terser": "^5.31.0"
|
|
98
|
+
},
|
|
99
|
+
"keywords": ["generative", "glsl", "webgl", "shader"],
|
|
100
|
+
"author": "",
|
|
101
|
+
"license": "ISC"
|
|
102
|
+
}`;
|
|
103
|
+
|
|
104
|
+
export const ml5HandTrackingPackageJsonContent = `{
|
|
105
|
+
"name": "project-name",
|
|
106
|
+
"version": "1.0.0",
|
|
107
|
+
"description": "ML5.js hand tracking project",
|
|
108
|
+
"main": "index.js",
|
|
109
|
+
"scripts": {
|
|
110
|
+
"start": "live-server",
|
|
111
|
+
"build": "node build.js"
|
|
112
|
+
},
|
|
113
|
+
"devDependencies": {
|
|
114
|
+
"live-server": "^1.2.1",
|
|
115
|
+
"terser": "^5.31.0"
|
|
116
|
+
},
|
|
117
|
+
"keywords": ["generative", "ml5", "hand-tracking", "computer-vision"],
|
|
118
|
+
"author": "",
|
|
119
|
+
"license": "ISC"
|
|
120
|
+
}`;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Build script template - shared across all project types
|
|
2
|
+
export const buildScriptContent = `#!/usr/bin/env node
|
|
3
|
+
// build.js - Bundle project into single HTML file
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { minify } = require('terser');
|
|
7
|
+
|
|
8
|
+
async function build() {
|
|
9
|
+
console.log('Building single-file bundle...');
|
|
10
|
+
|
|
11
|
+
const templateType = detectTemplateType();
|
|
12
|
+
console.log('Detected template type:', templateType);
|
|
13
|
+
|
|
14
|
+
// Read source files
|
|
15
|
+
const html = fs.readFileSync('index.html', 'utf8');
|
|
16
|
+
const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
|
|
17
|
+
const random = fs.readFileSync('scripts/random.js', 'utf8');
|
|
18
|
+
|
|
19
|
+
// Read library based on template type
|
|
20
|
+
let library = '';
|
|
21
|
+
let libraryFile = '';
|
|
22
|
+
|
|
23
|
+
switch (templateType) {
|
|
24
|
+
case 'default':
|
|
25
|
+
libraryFile = 'scripts/p5.js';
|
|
26
|
+
if (fs.existsSync(libraryFile)) {
|
|
27
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
break;
|
|
30
|
+
case 'svg':
|
|
31
|
+
libraryFile = 'scripts/paper-full.js';
|
|
32
|
+
if (fs.existsSync(libraryFile)) {
|
|
33
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case 'three':
|
|
37
|
+
libraryFile = 'scripts/three.module.js';
|
|
38
|
+
if (fs.existsSync(libraryFile)) {
|
|
39
|
+
library = fs.readFileSync(libraryFile, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
case 'glsl':
|
|
43
|
+
// No library needed - uses native WebGL
|
|
44
|
+
break;
|
|
45
|
+
case 'ml5-hand':
|
|
46
|
+
console.warn('\\n⚠️ Warning: ML5 template requires internet for AI models.');
|
|
47
|
+
console.warn(' The bundled file will keep the CDN reference to ml5.js.\\n');
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Minify JavaScript
|
|
52
|
+
let minifiedLibrary = '';
|
|
53
|
+
if (library) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await minify(library, {
|
|
56
|
+
compress: true,
|
|
57
|
+
mangle: true,
|
|
58
|
+
format: { comments: false }
|
|
59
|
+
});
|
|
60
|
+
minifiedLibrary = result.code;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.warn('Could not minify library, using original:', e.message);
|
|
63
|
+
minifiedLibrary = library;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let minifiedSketch = sketch;
|
|
68
|
+
let minifiedRandom = random;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// For Three.js, transform ES6 imports before minifying
|
|
72
|
+
let sketchToMinify = sketch;
|
|
73
|
+
if (templateType === 'three') {
|
|
74
|
+
sketchToMinify = transformThreeJsSketch(sketch);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sketchResult = await minify(sketchToMinify, {
|
|
78
|
+
compress: true,
|
|
79
|
+
mangle: true,
|
|
80
|
+
format: { comments: false }
|
|
81
|
+
});
|
|
82
|
+
minifiedSketch = sketchResult.code;
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.warn('Could not minify sketch.js, using original:', e.message);
|
|
85
|
+
if (templateType === 'three') {
|
|
86
|
+
minifiedSketch = transformThreeJsSketch(sketch);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const randomResult = await minify(random, {
|
|
92
|
+
compress: true,
|
|
93
|
+
mangle: true,
|
|
94
|
+
format: { comments: false }
|
|
95
|
+
});
|
|
96
|
+
minifiedRandom = randomResult.code;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.warn('Could not minify random.js, using original:', e.message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build the single HTML file
|
|
102
|
+
const bundledHtml = buildHtml(html, {
|
|
103
|
+
library: minifiedLibrary,
|
|
104
|
+
sketch: minifiedSketch,
|
|
105
|
+
random: minifiedRandom,
|
|
106
|
+
templateType
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Write output
|
|
110
|
+
fs.mkdirSync('dist', { recursive: true });
|
|
111
|
+
fs.writeFileSync('dist/bundle.html', bundledHtml);
|
|
112
|
+
|
|
113
|
+
const originalSize = Buffer.byteLength(html, 'utf8') +
|
|
114
|
+
Buffer.byteLength(sketch, 'utf8') +
|
|
115
|
+
Buffer.byteLength(random, 'utf8') +
|
|
116
|
+
Buffer.byteLength(library, 'utf8');
|
|
117
|
+
const bundleSize = fs.statSync('dist/bundle.html').size;
|
|
118
|
+
|
|
119
|
+
console.log('\\n✓ Bundle created: dist/bundle.html');
|
|
120
|
+
console.log(' Original size: ' + (originalSize / 1024).toFixed(1) + ' KB');
|
|
121
|
+
console.log(' Bundle size: ' + (bundleSize / 1024).toFixed(1) + ' KB');
|
|
122
|
+
console.log('\\nOpen dist/bundle.html in any browser - no server needed!');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function detectTemplateType() {
|
|
126
|
+
const html = fs.readFileSync('index.html', 'utf8');
|
|
127
|
+
if (html.includes('three.module.js') || html.includes('importmap')) return 'three';
|
|
128
|
+
if (html.includes('paper-full.js')) return 'svg';
|
|
129
|
+
if (html.includes('ml5')) return 'ml5-hand';
|
|
130
|
+
if (html.includes('p5.js')) return 'default';
|
|
131
|
+
// GLSL uses WebGL directly
|
|
132
|
+
if (html.includes('webgl') || html.includes('WebGL') || html.includes('gl.createShader')) return 'glsl';
|
|
133
|
+
// Check sketch.js for WebGL patterns
|
|
134
|
+
const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
|
|
135
|
+
if (sketch.includes('getContext(\\'webgl\\')') || sketch.includes('gl.createShader')) return 'glsl';
|
|
136
|
+
return 'default';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function transformThreeJsSketch(sketch) {
|
|
140
|
+
// Remove ES6 import statement
|
|
141
|
+
let transformed = sketch.replace(/import\\s+\\*\\s+as\\s+THREE\\s+from\\s+['\"]three['\"];?\\n?/g, '');
|
|
142
|
+
transformed = transformed.replace(/import\\s+\\{[^}]+\\}\\s+from\\s+['\"]three['\"];?\\n?/g, '');
|
|
143
|
+
return transformed;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildHtml(originalHtml, options) {
|
|
147
|
+
const { library, sketch, random, templateType } = options;
|
|
148
|
+
|
|
149
|
+
// Extract the style content
|
|
150
|
+
const styleMatch = originalHtml.match(/<style>([\\s\\S]*?)<\\/style>/);
|
|
151
|
+
const styles = styleMatch ? styleMatch[1] : '';
|
|
152
|
+
|
|
153
|
+
// Extract title
|
|
154
|
+
const titleMatch = originalHtml.match(/<title>([^<]*)<\\/title>/);
|
|
155
|
+
const title = titleMatch ? titleMatch[1] : 'Generative Sketch';
|
|
156
|
+
|
|
157
|
+
// Build based on template type
|
|
158
|
+
if (templateType === 'three') {
|
|
159
|
+
// Three.js needs special handling - wrap library as IIFE
|
|
160
|
+
const wrappedLibrary = wrapThreeJsAsGlobal(library);
|
|
161
|
+
return \`<!DOCTYPE html>
|
|
162
|
+
<html>
|
|
163
|
+
<head>
|
|
164
|
+
<meta charset="utf-8">
|
|
165
|
+
<title>\${title}</title>
|
|
166
|
+
<style>\${styles}</style>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
<script>\${wrappedLibrary}</script>
|
|
170
|
+
<script>\${random}</script>
|
|
171
|
+
<script>\${sketch}</script>
|
|
172
|
+
</body>
|
|
173
|
+
</html>\`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (templateType === 'ml5-hand') {
|
|
177
|
+
// Keep CDN reference for ML5
|
|
178
|
+
return \`<!DOCTYPE html>
|
|
179
|
+
<html>
|
|
180
|
+
<head>
|
|
181
|
+
<meta charset="utf-8">
|
|
182
|
+
<title>\${title}</title>
|
|
183
|
+
<style>\${styles}</style>
|
|
184
|
+
</head>
|
|
185
|
+
<body>
|
|
186
|
+
<div id="container">
|
|
187
|
+
<video id="video" autoplay playsinline></video>
|
|
188
|
+
<canvas id="canvas"></canvas>
|
|
189
|
+
<div id="status">Initializing...</div>
|
|
190
|
+
</div>
|
|
191
|
+
<script src="https://unpkg.com/ml5@1/dist/ml5.min.js"></script>
|
|
192
|
+
<script>\${random}</script>
|
|
193
|
+
<script>\${sketch}</script>
|
|
194
|
+
</body>
|
|
195
|
+
</html>\`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (templateType === 'svg') {
|
|
199
|
+
// Paper.js template
|
|
200
|
+
return \`<!DOCTYPE html>
|
|
201
|
+
<html>
|
|
202
|
+
<head>
|
|
203
|
+
<meta charset="utf-8">
|
|
204
|
+
<title>\${title}</title>
|
|
205
|
+
<style>\${styles}</style>
|
|
206
|
+
</head>
|
|
207
|
+
<body>
|
|
208
|
+
<canvas id="canvas" resize></canvas>
|
|
209
|
+
<script>\${library}</script>
|
|
210
|
+
<script>\${random}</script>
|
|
211
|
+
<script>\${sketch}</script>
|
|
212
|
+
</body>
|
|
213
|
+
</html>\`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (templateType === 'glsl') {
|
|
217
|
+
// GLSL template - no library
|
|
218
|
+
return \`<!DOCTYPE html>
|
|
219
|
+
<html>
|
|
220
|
+
<head>
|
|
221
|
+
<meta charset="utf-8">
|
|
222
|
+
<title>\${title}</title>
|
|
223
|
+
<style>\${styles}</style>
|
|
224
|
+
</head>
|
|
225
|
+
<body>
|
|
226
|
+
<canvas id="canvas"></canvas>
|
|
227
|
+
<script>\${random}</script>
|
|
228
|
+
<script>\${sketch}</script>
|
|
229
|
+
</body>
|
|
230
|
+
</html>\`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Default p5.js template
|
|
234
|
+
return \`<!DOCTYPE html>
|
|
235
|
+
<html>
|
|
236
|
+
<head>
|
|
237
|
+
<meta charset="utf-8">
|
|
238
|
+
<title>\${title}</title>
|
|
239
|
+
<style>\${styles}</style>
|
|
240
|
+
</head>
|
|
241
|
+
<body>
|
|
242
|
+
<script>\${library}</script>
|
|
243
|
+
<script>\${random}</script>
|
|
244
|
+
<script>\${sketch}</script>
|
|
245
|
+
</body>
|
|
246
|
+
</html>\`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function wrapThreeJsAsGlobal(threeCode) {
|
|
250
|
+
// Wrap Three.js module code to expose THREE as global
|
|
251
|
+
// The three.module.js exports everything, we capture it
|
|
252
|
+
return \`(function() {
|
|
253
|
+
var exports = {};
|
|
254
|
+
var module = { exports: exports };
|
|
255
|
+
(function(exports, module) {
|
|
256
|
+
\${threeCode}
|
|
257
|
+
})(exports, module);
|
|
258
|
+
window.THREE = exports.THREE || exports;
|
|
259
|
+
// Also expose common exports directly
|
|
260
|
+
if (exports.Scene) window.THREE = exports;
|
|
261
|
+
})();\`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
build().catch(err => {
|
|
265
|
+
console.error('Build failed:', err);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
268
|
+
`;
|