fragment-tools 0.1.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/LICENSE.md +21 -0
- package/README.md +101 -0
- package/bin/index.js +19 -0
- package/docs/README.md +18 -0
- package/docs/api/CLI.md +44 -0
- package/docs/api/renderers.md +80 -0
- package/docs/api/sketch.md +216 -0
- package/docs/api/triggers.md +101 -0
- package/docs/guide/about.md +16 -0
- package/docs/guide/exports.md +86 -0
- package/docs/guide/external-dependencies.md +22 -0
- package/docs/guide/getting-started.md +113 -0
- package/docs/guide/hot-shader-reloading.md +20 -0
- package/docs/guide/shortcuts.md +12 -0
- package/docs/guide/triggers.png +0 -0
- package/docs/guide/using-triggers.md +39 -0
- package/examples/cube-three.js +34 -0
- package/examples/ellipse-p5.js +26 -0
- package/examples/icon.fs +96 -0
- package/examples/icon.js +63 -0
- package/examples/icon.png +0 -0
- package/examples/icon.transparent.png +0 -0
- package/examples/package-lock.json +40 -0
- package/examples/package.json +15 -0
- package/examples/shape-2d.js +45 -0
- package/examples/shape-three.js +49 -0
- package/examples/shape-tree.fs +3 -0
- package/package.json +37 -0
- package/screenshot.png +0 -0
- package/src/cli/db.js +17 -0
- package/src/cli/index.js +198 -0
- package/src/cli/log.js +26 -0
- package/src/cli/plugins/check-dependencies.js +77 -0
- package/src/cli/plugins/db.js +12 -0
- package/src/cli/plugins/hot-shader-reload.js +86 -0
- package/src/cli/plugins/hot-sketch-reload.js +39 -0
- package/src/cli/plugins/screenshot.js +31 -0
- package/src/cli/server.js +140 -0
- package/src/cli/templates/2d.js +15 -0
- package/src/cli/templates/fragment.fs +10 -0
- package/src/cli/templates/fragment.js +18 -0
- package/src/cli/templates/index.js +24 -0
- package/src/cli/templates/p5.js +13 -0
- package/src/cli/templates/three-fragment.js +53 -0
- package/src/cli/templates/three-orthographic.js +23 -0
- package/src/cli/templates/three-perspective.js +20 -0
- package/src/cli/ws.js +92 -0
- package/src/client/app/App.svelte +8 -0
- package/src/client/app/client.js +68 -0
- package/src/client/app/components/IconCross.svelte +29 -0
- package/src/client/app/components/Init.svelte +13 -0
- package/src/client/app/components/KeyBinding.svelte +32 -0
- package/src/client/app/inputs/Input.js +15 -0
- package/src/client/app/inputs/Keyboard.js +21 -0
- package/src/client/app/inputs/MIDI.js +144 -0
- package/src/client/app/inputs/Mouse.js +5 -0
- package/src/client/app/inputs/Webcam.js +98 -0
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +88 -0
- package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +56 -0
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +40 -0
- package/src/client/app/lib/canvas-recorder/GIFRecorder.js +52 -0
- package/src/client/app/lib/canvas-recorder/MP4Recorder.js +46 -0
- package/src/client/app/lib/canvas-recorder/WebMRecorder.js +30 -0
- package/src/client/app/lib/canvas-recorder/mp4.js +20 -0
- package/src/client/app/lib/canvas-recorder/mp4.wasm +0 -0
- package/src/client/app/lib/canvas-recorder/utils.js +22 -0
- package/src/client/app/lib/gl/Geometry.js +39 -0
- package/src/client/app/lib/gl/Program.js +130 -0
- package/src/client/app/lib/gl/Renderer.js +148 -0
- package/src/client/app/lib/gl/Texture.js +114 -0
- package/src/client/app/lib/gl/index.js +109 -0
- package/src/client/app/lib/gl/utils.js +5 -0
- package/src/client/app/lib/helpers/frameDebounce.js +40 -0
- package/src/client/app/lib/loader/index.js +20 -0
- package/src/client/app/lib/loader/loadImage.js +19 -0
- package/src/client/app/lib/loader/loadScript.js +14 -0
- package/src/client/app/lib/paper-sizes.js +104 -0
- package/src/client/app/lib/presets.js +12 -0
- package/src/client/app/lib/tempo/Analyser.js +165 -0
- package/src/client/app/lib/tempo/Range.js +97 -0
- package/src/client/app/lib/tempo/index.js +138 -0
- package/src/client/app/modules/AudioAnalyser/Range.svelte +93 -0
- package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +31 -0
- package/src/client/app/modules/AudioAnalyser.svelte +70 -0
- package/src/client/app/modules/Console/ConsoleLine.svelte +254 -0
- package/src/client/app/modules/Console.svelte +82 -0
- package/src/client/app/modules/Exports.svelte +105 -0
- package/src/client/app/modules/MidiPanel.svelte +106 -0
- package/src/client/app/modules/Monitor.svelte +62 -0
- package/src/client/app/modules/Params.svelte +112 -0
- package/src/client/app/renderers/2DRenderer.js +5 -0
- package/src/client/app/renderers/FragmentRenderer.js +62 -0
- package/src/client/app/renderers/OGLRenderer.js +0 -0
- package/src/client/app/renderers/P5Renderer.js +39 -0
- package/src/client/app/renderers/THREERenderer.js +128 -0
- package/src/client/app/stores/audioAnalysis.js +10 -0
- package/src/client/app/stores/console.js +76 -0
- package/src/client/app/stores/errors.js +25 -0
- package/src/client/app/stores/exports.js +28 -0
- package/src/client/app/stores/index.js +2 -0
- package/src/client/app/stores/layout.js +187 -0
- package/src/client/app/stores/multisampling.js +16 -0
- package/src/client/app/stores/props.js +44 -0
- package/src/client/app/stores/renderers.js +60 -0
- package/src/client/app/stores/rendering.js +111 -0
- package/src/client/app/stores/sketches.js +40 -0
- package/src/client/app/stores/time.js +27 -0
- package/src/client/app/stores/utils.js +66 -0
- package/src/client/app/transitions/fade.js +17 -0
- package/src/client/app/transitions/index.js +12 -0
- package/src/client/app/transitions/splitX.js +16 -0
- package/src/client/app/transitions/splitY.js +16 -0
- package/src/client/app/triggers/Keyboard.js +95 -0
- package/src/client/app/triggers/MIDI.js +122 -0
- package/src/client/app/triggers/Mouse.js +96 -0
- package/src/client/app/triggers/Trigger.js +71 -0
- package/src/client/app/triggers/index.js +19 -0
- package/src/client/app/triggers/shared.js +37 -0
- package/src/client/app/ui/Build.svelte +96 -0
- package/src/client/app/ui/ErrorOverlay.svelte +130 -0
- package/src/client/app/ui/Field.svelte +262 -0
- package/src/client/app/ui/FieldGroup.svelte +103 -0
- package/src/client/app/ui/FieldSection.svelte +123 -0
- package/src/client/app/ui/FieldSpace.svelte +37 -0
- package/src/client/app/ui/FieldTrigger.svelte +263 -0
- package/src/client/app/ui/FieldTriggers.svelte +58 -0
- package/src/client/app/ui/FloatingParams.svelte +49 -0
- package/src/client/app/ui/Layout.svelte +50 -0
- package/src/client/app/ui/LayoutColumn.svelte +9 -0
- package/src/client/app/ui/LayoutComponent.svelte +279 -0
- package/src/client/app/ui/LayoutResizer.svelte +218 -0
- package/src/client/app/ui/LayoutRoot.svelte +11 -0
- package/src/client/app/ui/LayoutRow.svelte +9 -0
- package/src/client/app/ui/LayoutToolbar.svelte +264 -0
- package/src/client/app/ui/Module.svelte +154 -0
- package/src/client/app/ui/ModuleHeaderAction.svelte +87 -0
- package/src/client/app/ui/ModuleHeaderButton.svelte +21 -0
- package/src/client/app/ui/ModuleHeaderSelect.svelte +50 -0
- package/src/client/app/ui/ModuleRenderer.svelte +38 -0
- package/src/client/app/ui/OutputRenderer.svelte +149 -0
- package/src/client/app/ui/ParamsMultisampling.svelte +109 -0
- package/src/client/app/ui/ParamsOutput.svelte +139 -0
- package/src/client/app/ui/Preview.svelte +15 -0
- package/src/client/app/ui/SelectChevrons.svelte +25 -0
- package/src/client/app/ui/SketchRenderer.svelte +672 -0
- package/src/client/app/ui/SketchSelect.svelte +49 -0
- package/src/client/app/ui/fields/ButtonInput.svelte +54 -0
- package/src/client/app/ui/fields/CheckboxInput.svelte +70 -0
- package/src/client/app/ui/fields/ColorInput.svelte +187 -0
- package/src/client/app/ui/fields/FieldInputRow.svelte +13 -0
- package/src/client/app/ui/fields/ImageInput.svelte +145 -0
- package/src/client/app/ui/fields/Input.svelte +120 -0
- package/src/client/app/ui/fields/ListInput.svelte +106 -0
- package/src/client/app/ui/fields/NumberInput.svelte +114 -0
- package/src/client/app/ui/fields/ProgressInput.svelte +90 -0
- package/src/client/app/ui/fields/Select.svelte +116 -0
- package/src/client/app/ui/fields/TextInput.svelte +18 -0
- package/src/client/app/ui/fields/Vec2Input.svelte +5 -0
- package/src/client/app/ui/fields/Vec3Input.svelte +6 -0
- package/src/client/app/ui/fields/VectorInput.svelte +102 -0
- package/src/client/app/utils/canvas.utils.js +229 -0
- package/src/client/app/utils/color.utils.js +427 -0
- package/src/client/app/utils/file.utils.js +77 -0
- package/src/client/app/utils/glsl.utils.js +14 -0
- package/src/client/app/utils/glslErrors.js +154 -0
- package/src/client/app/utils/index.js +39 -0
- package/src/client/app/utils/math.utils.js +23 -0
- package/src/client/app/utils/props.utils.js +53 -0
- package/src/client/index.html +18 -0
- package/src/client/main.js +9 -0
- package/src/client/public/css/global.css +115 -0
- package/src/client/public/favicon.ico +0 -0
- package/src/client/public/fonts/Inter-Bold.woff2 +0 -0
- package/src/client/public/fonts/Inter-Italic.woff2 +0 -0
- package/src/client/public/fonts/Inter-Regular.woff2 +0 -0
- package/src/client/public/fonts/Inter-SemiBold.woff2 +0 -0
- package/src/client/public/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/client/public/icons/chevron-bottom.svg +3 -0
- package/src/client/public/icons/chevron-right.svg +3 -0
- package/src/client/public/icons/chevron-top.svg +3 -0
- package/src/client/public/icons/columns-horizontal.svg +4 -0
- package/src/client/public/icons/columns-vertical.svg +4 -0
- package/src/client/public/icons/folder-plus.svg +6 -0
- package/src/client/public/icons/lock.svg +4 -0
- package/src/client/public/icons/picture-in-picture.svg +4 -0
- package/src/client/public/icons/trash.svg +5 -0
- package/src/client/public/icons/trigger.svg +8 -0
- package/src/client/public/icons/unlock.svg +4 -0
- package/src/client/public/js/ffmpeg.min.js +2 -0
- package/src/client/public/js/ffmpeg.min.js.map +1 -0
- package/src/client/public/js/gif.js +2 -0
- package/src/client/public/js/gif.worker.js +2 -0
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fragment-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A web development environment for creative coding",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fragment": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/raphaelameaume/fragment.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "Raphaël Améaume",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/raphaelameaume/fragment/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/raphaelameaume/fragment#readme",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@rollup/plugin-alias": "^3.1.5",
|
|
22
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.2",
|
|
23
|
+
"body-parser": "^1.20.0",
|
|
24
|
+
"changedpi": "^1.0.4",
|
|
25
|
+
"chokidar": "^3.5.2",
|
|
26
|
+
"convert-length": "^1.0.1",
|
|
27
|
+
"get-port": "^6.0.0",
|
|
28
|
+
"glslify": "^7.1.1",
|
|
29
|
+
"kleur": "^4.1.4",
|
|
30
|
+
"sade": "^1.7.4",
|
|
31
|
+
"svelte": "^3.49.0",
|
|
32
|
+
"svelte-json-tree": "^1.0.0",
|
|
33
|
+
"vite": "^3.0.9",
|
|
34
|
+
"webm-writer": "^1.0.0",
|
|
35
|
+
"ws": "^8.2.3"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/screenshot.png
ADDED
|
Binary file
|
package/src/cli/db.js
ADDED
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import log from "./log.js";
|
|
4
|
+
import { start as startViteServer } from "./server.js";
|
|
5
|
+
import { start as startWebSocketServer } from "./ws.js";
|
|
6
|
+
import templates from "./templates/index.js";
|
|
7
|
+
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const timestamp = Date.now();
|
|
13
|
+
|
|
14
|
+
export const run = async (entry, options) => {
|
|
15
|
+
let wsServer;
|
|
16
|
+
|
|
17
|
+
function exit() {
|
|
18
|
+
process.off('SIGTERM', exit);
|
|
19
|
+
|
|
20
|
+
if (wsServer) {
|
|
21
|
+
wsServer.close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.once('SIGTERM', exit);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const entries = await createEntries(entry, options);
|
|
29
|
+
|
|
30
|
+
if (entries.length > 0) {
|
|
31
|
+
const filepaths = await generateFiles(entries, options);
|
|
32
|
+
|
|
33
|
+
if (!options.build) {
|
|
34
|
+
wsServer = await startWebSocketServer({
|
|
35
|
+
cwd: process.cwd(),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await startViteServer({
|
|
40
|
+
options,
|
|
41
|
+
timestamp,
|
|
42
|
+
filepaths,
|
|
43
|
+
entries,
|
|
44
|
+
fragment: {
|
|
45
|
+
server: wsServer,
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
} catch(error) {
|
|
50
|
+
console.log(error);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async function createEntries(entry, options) {
|
|
55
|
+
if (entry === undefined) {
|
|
56
|
+
log.error(`Missing argument.`)
|
|
57
|
+
console.log(" Use --new flag to let fragment create the file.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const entries = [];
|
|
62
|
+
let shouldCreateFile = options.new;
|
|
63
|
+
|
|
64
|
+
async function createEntryFile(entryPath) {
|
|
65
|
+
if (path.extname(entryPath) !== ".js") {
|
|
66
|
+
throw new Error(`File extension needs to be .js`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const createFromTemplate = typeof options.template === "string";
|
|
70
|
+
const templateFiles = createFromTemplate ? templates[options.template] : templates.default;
|
|
71
|
+
|
|
72
|
+
if (!templateFiles) {
|
|
73
|
+
throw new Error(`Error: Template ${options.template} doesn't exist.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const entryName = path.basename(entryPath, path.extname(entryPath));
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < templateFiles.length; i++) {
|
|
79
|
+
const filepath = path.join(__dirname, templateFiles[i]);
|
|
80
|
+
const ext = path.extname(filepath);
|
|
81
|
+
let fileContent = (await fs.readFile(filepath)).toString();
|
|
82
|
+
|
|
83
|
+
const destPath = i === 0 ? entryPath :
|
|
84
|
+
path.join(process.cwd(), `${entryName}${ext}`);
|
|
85
|
+
const destName = path.basename(destPath);
|
|
86
|
+
|
|
87
|
+
const filepaths = templateFiles
|
|
88
|
+
.filter((file, index) => index !== i)
|
|
89
|
+
.map((file) => path.basename(file));
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < filepaths.length; i++) {
|
|
92
|
+
fileContent = fileContent.replace(new RegExp(filepaths[i], 'g'), `${entryName}${path.extname(filepaths[i])}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const stats = await fs.lstat(destPath);
|
|
97
|
+
|
|
98
|
+
if (stats.isFile()) {
|
|
99
|
+
log.warning(`Ignored argument: --new. File already exists.`);
|
|
100
|
+
}
|
|
101
|
+
} catch(error) {
|
|
102
|
+
if (error.code === "ENOENT") {
|
|
103
|
+
await fs.writeFile(destPath, Buffer.from(fileContent));
|
|
104
|
+
console.log(`${log.prefix} Created ${path.relative(process.cwd(), destPath)} on disk.`);
|
|
105
|
+
} else {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
shouldCreateFile = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const entryPath = path.join(process.cwd(), entry);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (shouldCreateFile) {
|
|
118
|
+
await createEntryFile(entryPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const stats = await fs.lstat(entryPath);
|
|
122
|
+
|
|
123
|
+
if (stats.isFile()) {
|
|
124
|
+
entries.push(path.relative(process.cwd(), entryPath));
|
|
125
|
+
} else if (stats.isDirectory()) {
|
|
126
|
+
const files = await fs.readdir(entryPath);
|
|
127
|
+
const sketchFiles = files.filter((file) => path.extname(file) === ".js");
|
|
128
|
+
|
|
129
|
+
if (sketchFiles.length === 0) {
|
|
130
|
+
log.error(`Folder doesn't contain any sketch files.`);
|
|
131
|
+
console.log("Use --new flag to start working on a sketch.");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
entries.push(...sketchFiles.map((sketchFile) => path.relative(process.cwd(), sketchFile)));
|
|
136
|
+
}
|
|
137
|
+
} catch(error) {
|
|
138
|
+
if (error.code === "ENOENT") {
|
|
139
|
+
log.error(`Error: ${entry} doesn't exist.`);
|
|
140
|
+
} else {
|
|
141
|
+
log.error(error.message);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
return entries;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function generateFiles(entries, options) {
|
|
151
|
+
log.warning(`Building files for:`);
|
|
152
|
+
entries.forEach(entry => console.log(`- ${entry}`));
|
|
153
|
+
|
|
154
|
+
const dir = "/node_modules/.fragment";
|
|
155
|
+
const dirpath = path.join(process.cwd(), dir);
|
|
156
|
+
const filename = `sketches.js`;
|
|
157
|
+
|
|
158
|
+
// create directory and don't throw error if it already exists
|
|
159
|
+
try {
|
|
160
|
+
await fs.mkdir(dirpath, { recursive: true });
|
|
161
|
+
} catch (e) {
|
|
162
|
+
if (e.code !== 'EEXIST') {
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// generate sketch index file
|
|
168
|
+
const code = `
|
|
169
|
+
// This file is generated by Fragment. Do not edit it.
|
|
170
|
+
|
|
171
|
+
export const sketches = {
|
|
172
|
+
${entries.map((entry) => {
|
|
173
|
+
return `"${entry}": () => import("../../${entry}")`
|
|
174
|
+
}).join(',')
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const onSketchReload = (fn) => {
|
|
179
|
+
if (import.meta.hot) {
|
|
180
|
+
import.meta.hot.data.onSketchChange = fn;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (import.meta.hot) {
|
|
185
|
+
import.meta.hot.accept((m) => {
|
|
186
|
+
if (typeof import.meta.hot.data.onSketchChange === "function") {
|
|
187
|
+
import.meta.hot.data.onSketchChange(m);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const filepath = path.join(dirpath, filename);
|
|
194
|
+
|
|
195
|
+
await fs.writeFile(filepath, code);
|
|
196
|
+
|
|
197
|
+
return [filepath];
|
|
198
|
+
}
|
package/src/cli/log.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import kleur from "kleur";
|
|
2
|
+
|
|
3
|
+
const log = (() => {
|
|
4
|
+
let prefix = kleur.grey('[fragment]');
|
|
5
|
+
|
|
6
|
+
const success = (message) => {
|
|
7
|
+
console.log(prefix, kleur.green(`✔ ${message}`));
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const warning = (message) => {
|
|
11
|
+
console.log(prefix, kleur.yellow(`ℹ ${message}`));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const error = (message) => {
|
|
15
|
+
console.log(prefix, kleur.red(`✖ ${message}`));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
prefix,
|
|
20
|
+
success,
|
|
21
|
+
warning,
|
|
22
|
+
error,
|
|
23
|
+
};
|
|
24
|
+
})();
|
|
25
|
+
|
|
26
|
+
export default log;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import log from "../log.js";
|
|
4
|
+
|
|
5
|
+
export default function checkDependencies({ cwd, app, entriesPaths, build } = {}) {
|
|
6
|
+
const regex = /\bexport[\s]*\b(let|const)[\s]*\brendering\b[\s]*=[\s]*["'](.*?)["']/;
|
|
7
|
+
|
|
8
|
+
const dependenciesMap = new Map();
|
|
9
|
+
dependenciesMap.set("three", ["three"]);
|
|
10
|
+
dependenciesMap.set("p5", ["p5"]);
|
|
11
|
+
dependenciesMap.set("fragment", []);
|
|
12
|
+
dependenciesMap.set("2d", []);
|
|
13
|
+
|
|
14
|
+
const renderers = new Map();
|
|
15
|
+
renderers.set("three", `${app}/renderers/THREERenderer.js`);
|
|
16
|
+
renderers.set("p5", `${app}/renderers/P5Renderer.js`);
|
|
17
|
+
renderers.set("ogl", `${app}/renderers/OGLRenderer.js`);
|
|
18
|
+
renderers.set("fragment", `${app}/renderers/FragmentRenderer.js`);
|
|
19
|
+
renderers.set("2d", `${app}/renderers/2DRenderer.js`);
|
|
20
|
+
|
|
21
|
+
const renderings = [];
|
|
22
|
+
|
|
23
|
+
entriesPaths.forEach((entry) => {
|
|
24
|
+
const content = fs.readFileSync(entry, { encoding: "utf-8" });
|
|
25
|
+
const match = content.match(regex);
|
|
26
|
+
const rendering = match && match[2];
|
|
27
|
+
|
|
28
|
+
if (rendering) {
|
|
29
|
+
renderings.push(rendering);
|
|
30
|
+
|
|
31
|
+
const dependencies = dependenciesMap.get(rendering);
|
|
32
|
+
dependencies.forEach((dependency) => {
|
|
33
|
+
const dependencyPath = path.join(cwd, `node_modules/${dependency}`);
|
|
34
|
+
const isInstalled = fs.existsSync(dependencyPath);
|
|
35
|
+
|
|
36
|
+
if (!isInstalled) {
|
|
37
|
+
const filename = entry.split(`${cwd}/`)[1];
|
|
38
|
+
const error = `Missing dependency "${dependency}" in ${filename}`;
|
|
39
|
+
log.error(error);
|
|
40
|
+
console.log(`
|
|
41
|
+
It looks like you're trying to build a ${dependency} sketch (${filename}) without having ${dependency} installed.
|
|
42
|
+
Run 'npm install ${dependency}' before starting Fragment to run ${filename}.
|
|
43
|
+
`)
|
|
44
|
+
|
|
45
|
+
throw new Error(error);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const skipFiles = [...renderers.keys()]
|
|
52
|
+
.map((key) => {
|
|
53
|
+
if (!renderings.includes(key)) {
|
|
54
|
+
return renderers.get(key);
|
|
55
|
+
} else {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.flat();
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
name: 'check-dependencies',
|
|
63
|
+
config: () => ({
|
|
64
|
+
define: {
|
|
65
|
+
'__THREE_RENDERER__': build ? renderings.some((rendering) => rendering === "three") : true,
|
|
66
|
+
'__P5_RENDERER__': build ? renderings.some((rendering) => rendering === "p5") : true,
|
|
67
|
+
'__FRAGMENT_RENDERER__': build ? renderings.some((rendering) => rendering === "fragment") : true,
|
|
68
|
+
'__2D_RENDERER__': build ? renderings.some((rendering) => rendering === "2d") : true,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
load(id) {
|
|
72
|
+
if (build && skipFiles.includes(id)) {
|
|
73
|
+
return { code: "", map: null };
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import glslify from "glslify";
|
|
3
|
+
|
|
4
|
+
export default function hotShaderReload({ wss }) {
|
|
5
|
+
const fileRegex = /\.(?:frag|vert|glsl|vs|fs)$/
|
|
6
|
+
|
|
7
|
+
function addShaderFilepath(shader, id) {
|
|
8
|
+
let keyword = `void main`;
|
|
9
|
+
let shaderParts = shader.split(keyword);
|
|
10
|
+
let hint = `// <filepath://${id}>`;
|
|
11
|
+
|
|
12
|
+
return `${shaderParts[0]}
|
|
13
|
+
${hint}
|
|
14
|
+
${keyword}${shaderParts[1]}
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let modulesToReload = [];
|
|
19
|
+
|
|
20
|
+
function compile(shader) {
|
|
21
|
+
return glslify(shader, {
|
|
22
|
+
basedir: process.cwd()
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
name: 'hot-shader-reload',
|
|
28
|
+
config: () => ({
|
|
29
|
+
optimizeDeps: {
|
|
30
|
+
esbuildOptions: {
|
|
31
|
+
loader: {
|
|
32
|
+
".frag": "text",
|
|
33
|
+
".vert": "text",
|
|
34
|
+
".glsl": "text",
|
|
35
|
+
".fs": "text",
|
|
36
|
+
".vs": "text",
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
handleHotUpdate: async ({ modules, file, read }) => {
|
|
42
|
+
if (fileRegex.test(file)) {
|
|
43
|
+
let src = await read();
|
|
44
|
+
let source = compile(src);
|
|
45
|
+
source = addShaderFilepath(source, file);
|
|
46
|
+
|
|
47
|
+
wss.send({
|
|
48
|
+
type: 'custom',
|
|
49
|
+
event: 'shader-update',
|
|
50
|
+
data: {
|
|
51
|
+
filepath: file,
|
|
52
|
+
source
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// save modules that were handled
|
|
57
|
+
modulesToReload.push(...modules);
|
|
58
|
+
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// add previous modules that were handled to the list of modules to avoid shader invalidation
|
|
63
|
+
if (modulesToReload.length > 0) {
|
|
64
|
+
const all = [
|
|
65
|
+
...modules,
|
|
66
|
+
...modulesToReload,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
modulesToReload = [];
|
|
70
|
+
|
|
71
|
+
return all;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
transform: (src, id) => {
|
|
75
|
+
if (fileRegex.test(id)) {
|
|
76
|
+
let source = compile(src);
|
|
77
|
+
source = addShaderFilepath(source, id);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
code: `export default ${JSON.stringify(source)}`,
|
|
81
|
+
map: null // provide source map if available
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import log from "../log.js";
|
|
4
|
+
|
|
5
|
+
export default function hotSketchReload({ cwd }) {
|
|
6
|
+
return {
|
|
7
|
+
name: 'hot-sketch-reload',
|
|
8
|
+
handleHotUpdate: async ({ server, modules, file, read }) => {
|
|
9
|
+
if (file.includes(cwd)) {
|
|
10
|
+
const filepath = path.relative(cwd, file);
|
|
11
|
+
console.log(`${log.prefix} ${kleur.green(`hmr update`)} /${filepath}`);
|
|
12
|
+
|
|
13
|
+
server.ws.send({
|
|
14
|
+
type: 'custom',
|
|
15
|
+
event: 'sketch-update',
|
|
16
|
+
data: {
|
|
17
|
+
file,
|
|
18
|
+
filepath,
|
|
19
|
+
cwd,
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return modules;
|
|
26
|
+
},
|
|
27
|
+
// transform: (src, id) => {
|
|
28
|
+
// if (fileRegex.test(id)) {
|
|
29
|
+
// let source = glslify(src);
|
|
30
|
+
// source = addShaderFilepath(source, id);
|
|
31
|
+
|
|
32
|
+
// return {
|
|
33
|
+
// code: `export default ${JSON.stringify(source)}`,
|
|
34
|
+
// map: null // provide source map if available
|
|
35
|
+
// }
|
|
36
|
+
// }
|
|
37
|
+
// }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import bodyParser from "body-parser";
|
|
4
|
+
|
|
5
|
+
export default function screenshot({ cwd }) {
|
|
6
|
+
return {
|
|
7
|
+
name: 'screenshot',
|
|
8
|
+
configureServer(server){
|
|
9
|
+
server.middlewares.use(bodyParser.json({ limit: '100mb'}))
|
|
10
|
+
server.middlewares.use('/save', (req, res, next) => {
|
|
11
|
+
if (req.method === "POST") {
|
|
12
|
+
const { filename, dataURL } = req.body;
|
|
13
|
+
|
|
14
|
+
const filepath = path.join(cwd, filename);
|
|
15
|
+
const buffer = Buffer.from(dataURL, 'base64');
|
|
16
|
+
|
|
17
|
+
fs.writeFile(filepath, buffer, (error) => {
|
|
18
|
+
let statusCode = error ? 500 : 200;
|
|
19
|
+
let body = error ? { error } : { filepath };
|
|
20
|
+
|
|
21
|
+
res.writeHead(statusCode, {'Content-Type': 'application/json'});
|
|
22
|
+
res.end(JSON.stringify(body));
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
next();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { createServer, defineConfig, build } from "vite";
|
|
6
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
7
|
+
import hotShaderReload from "./plugins/hot-shader-reload.js";
|
|
8
|
+
import hotSketchReload from "./plugins/hot-sketch-reload.js";
|
|
9
|
+
import dbPlugin from "./plugins/db.js";
|
|
10
|
+
|
|
11
|
+
import log from "./log.js";
|
|
12
|
+
import db from "./db.js";
|
|
13
|
+
import screenshotPlugin from "./plugins/screenshot.js";
|
|
14
|
+
import checkDependencies from "./plugins/check-dependencies.js";
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
export async function start({ options, filepaths, entries, fragment }) {
|
|
20
|
+
const root = path.join(__dirname, '/../client');
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const app = path.join(root, 'app');
|
|
23
|
+
|
|
24
|
+
const entriesPaths = entries.map((entry ) => path.join(cwd, entry));
|
|
25
|
+
|
|
26
|
+
const config = defineConfig({
|
|
27
|
+
configFile: false,
|
|
28
|
+
root,
|
|
29
|
+
logLevel: options.development ? "info" : "silent",
|
|
30
|
+
resolve: {
|
|
31
|
+
alias: [
|
|
32
|
+
{ find: '@fragment/sketches', replacement: filepaths[0] },
|
|
33
|
+
{ find: '@fragment', replacement: app },
|
|
34
|
+
{ find: 'three', replacement: path.join(cwd, 'node_modules/three') },
|
|
35
|
+
{ find: 'p5', replacement: path.join(cwd, 'node_modules/p5') },
|
|
36
|
+
{ find: 'ogl', replacement: path.join(cwd, 'node_modules/ogl') },
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
plugins: [
|
|
40
|
+
svelte({
|
|
41
|
+
configFile: false,
|
|
42
|
+
onwarn: (warning, handler) => {
|
|
43
|
+
if (options.development) {
|
|
44
|
+
handler(warning);
|
|
45
|
+
} else {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
hotSketchReload({
|
|
51
|
+
cwd,
|
|
52
|
+
}),
|
|
53
|
+
hotShaderReload({ wss: fragment.server }),
|
|
54
|
+
{
|
|
55
|
+
name: 'configure-response-headers',
|
|
56
|
+
configureServer: (server) => {
|
|
57
|
+
server.middlewares.use((_req, res, next) => {
|
|
58
|
+
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
59
|
+
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
60
|
+
next();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
dbPlugin(),
|
|
65
|
+
screenshotPlugin({ cwd }),
|
|
66
|
+
checkDependencies({
|
|
67
|
+
cwd,
|
|
68
|
+
app,
|
|
69
|
+
entriesPaths,
|
|
70
|
+
build: options.build,
|
|
71
|
+
})
|
|
72
|
+
],
|
|
73
|
+
server: {
|
|
74
|
+
port: options.port,
|
|
75
|
+
host: true,
|
|
76
|
+
fs: {
|
|
77
|
+
strict: false,
|
|
78
|
+
allow: [".."],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
define: {
|
|
82
|
+
'__CWD__': `${JSON.stringify(cwd)}`,
|
|
83
|
+
'__FRAGMENT_PORT__': fragment.server ? fragment.server.port : undefined,
|
|
84
|
+
'__START_TIME__': Date.now(),
|
|
85
|
+
'__SEED__': Date.now(),
|
|
86
|
+
'__PRODUCTION__': options.build,
|
|
87
|
+
},
|
|
88
|
+
optimizeDeps: {
|
|
89
|
+
exclude: [
|
|
90
|
+
"@fragment/sketches",
|
|
91
|
+
...entriesPaths,
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (options.build) {
|
|
97
|
+
if (entries.length > 1) {
|
|
98
|
+
log.error(`fragment can only build one sketch at a time.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const outDir = options.outDir ? options.outDir : entries[0].split('.js')[0];
|
|
103
|
+
|
|
104
|
+
await build({
|
|
105
|
+
...config,
|
|
106
|
+
logLevel: "info",
|
|
107
|
+
build: {
|
|
108
|
+
outDir: path.join(process.cwd(), outDir),
|
|
109
|
+
emptyOutDir: options.emptyOutDir,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
log.success(`Built files for:`);
|
|
114
|
+
entries.forEach(entry => console.log(`- ${entry}`));
|
|
115
|
+
} else {
|
|
116
|
+
log.warning(`Starting server...`);
|
|
117
|
+
const server = await createServer(config);
|
|
118
|
+
|
|
119
|
+
server.middlewares.use('/db', (req, res, next) => {
|
|
120
|
+
next();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await server.listen();
|
|
124
|
+
log.success(`Server started at:`);
|
|
125
|
+
|
|
126
|
+
Object.values(os.networkInterfaces())
|
|
127
|
+
.flatMap((nInterface) => nInterface ?? [])
|
|
128
|
+
.filter((detail) => detail && detail.address && (detail.family === 'IPv4' || detail.family === 4 ))
|
|
129
|
+
.forEach((detail) => {
|
|
130
|
+
const type = detail.address.includes('127.0.0.1')
|
|
131
|
+
? 'Local: '
|
|
132
|
+
: 'Network: '
|
|
133
|
+
const host = detail.address.replace('127.0.0.1', 'localhost');
|
|
134
|
+
const url = `http://${host}:${server.config.server.port}`;
|
|
135
|
+
console.log(` ${type} ${url}`);
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return server;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export let props = {};
|
|
2
|
+
|
|
3
|
+
export let init = ({ context, width, height }) => {
|
|
4
|
+
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export let update = ({ context, width, height, time, deltaTime }) => {
|
|
8
|
+
context.clearRect(0, 0, width, height);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export let resize = ({ width, height }) => {
|
|
12
|
+
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export let rendering = "2d";
|