fragment-tools 0.1.13 → 0.1.15
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/.prettierignore +1 -2
- package/.prettierrc +23 -7
- package/README.md +28 -9
- package/bin/index.js +70 -10
- package/package.json +14 -6
- package/src/cli/build.js +125 -0
- package/src/cli/create.js +238 -0
- package/src/cli/createConfig.js +82 -0
- package/src/cli/createFragmentFile.js +70 -0
- package/src/cli/getEntries.js +85 -0
- package/src/cli/log.js +36 -24
- package/src/cli/plugins/check-dependencies.js +88 -42
- package/src/cli/plugins/hot-shader-replacement.js +408 -0
- package/src/cli/plugins/hot-sketch-reload.js +21 -25
- package/src/cli/plugins/save.js +101 -0
- package/src/cli/preview.js +55 -0
- package/src/cli/prompts.js +260 -0
- package/src/cli/run.js +131 -0
- package/src/cli/templates/blank/index.js +33 -0
- package/src/cli/templates/blank/meta.json +4 -0
- package/src/cli/templates/default/index.js +39 -0
- package/src/cli/templates/default/meta.json +5 -0
- package/src/cli/templates/fragment-gl/index.js +37 -0
- package/src/cli/templates/fragment-gl/meta.json +4 -0
- package/src/cli/templates/p5/index.js +32 -0
- package/src/cli/templates/p5/meta.json +5 -0
- package/src/cli/templates/p5-webgl/fragment.fs +14 -0
- package/src/cli/templates/p5-webgl/index.js +67 -0
- package/src/cli/templates/p5-webgl/meta.json +5 -0
- package/src/cli/templates/three-fragment/fragment.fs +10 -0
- package/src/cli/templates/three-fragment/index.js +95 -0
- package/src/cli/templates/three-fragment/meta.json +5 -0
- package/src/cli/templates/three-orthographic/index.js +55 -0
- package/src/cli/templates/three-orthographic/meta.json +5 -0
- package/src/cli/templates/three-perspective/index.js +52 -0
- package/src/cli/templates/three-perspective/meta.json +5 -0
- package/src/cli/utils.js +70 -0
- package/src/cli/ws.js +87 -78
- package/src/client/app/App.svelte +3 -3
- package/src/client/app/client.js +55 -39
- package/src/client/app/components/IconCross.svelte +18 -18
- package/src/client/app/components/Init.svelte +40 -8
- package/src/client/app/components/KeyBinding.svelte +22 -22
- package/src/client/app/helpers.js +42 -0
- package/src/client/app/hooks.js +20 -0
- package/src/client/app/inputs/Input.js +9 -9
- package/src/client/app/inputs/Keyboard.js +13 -15
- package/src/client/app/inputs/MIDI.js +14 -15
- package/src/client/app/inputs/Mouse.js +1 -1
- package/src/client/app/inputs/Webcam.js +89 -88
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
- package/src/client/app/lib/canvas-recorder/FrameRecorder.js +7 -6
- package/src/client/app/lib/canvas-recorder/H264Recorder.js +45 -0
- package/src/client/app/lib/canvas-recorder/MP4Recorder.js +7 -9
- package/src/client/app/lib/canvas-recorder/WebMRecorder.js +3 -4
- package/src/client/app/lib/canvas-recorder/mp4.js +1649 -15
- package/src/client/app/lib/canvas-recorder/utils.js +33 -17
- package/src/client/app/lib/gl/Geometry.js +11 -8
- package/src/client/app/lib/gl/Program.js +38 -19
- package/src/client/app/lib/gl/Renderer.js +163 -156
- package/src/client/app/lib/gl/Texture.js +113 -85
- package/src/client/app/lib/gl/index.js +12 -12
- package/src/client/app/lib/gl/utils.js +1 -3
- package/src/client/app/lib/helpers/frameDebounce.js +30 -30
- package/src/client/app/lib/loader/index.js +10 -10
- package/src/client/app/lib/loader/loadImage.js +15 -15
- package/src/client/app/lib/loader/loadScript.js +1 -1
- package/src/client/app/lib/paper-sizes.js +75 -76
- package/src/client/app/lib/presets.js +25 -5
- package/src/client/app/lib/tempo/Analyser.js +18 -17
- package/src/client/app/lib/tempo/Range.js +15 -12
- package/src/client/app/lib/tempo/index.js +34 -27
- package/src/client/app/modules/AudioAnalyser/Range.svelte +69 -72
- package/src/client/app/modules/AudioAnalyser/Spectrum.svelte +20 -19
- package/src/client/app/modules/AudioAnalyser.svelte +52 -35
- package/src/client/app/modules/Console/ConsoleLine.svelte +193 -172
- package/src/client/app/modules/Console.svelte +76 -74
- package/src/client/app/modules/Exports.svelte +62 -43
- package/src/client/app/modules/MidiPanel.svelte +100 -101
- package/src/client/app/modules/Monitor.svelte +57 -57
- package/src/client/app/modules/Params.svelte +128 -103
- package/src/client/app/renderers/2DRenderer.js +3 -3
- package/src/client/app/renderers/FragmentRenderer.js +30 -23
- package/src/client/app/renderers/P5GLRenderer.js +144 -0
- package/src/client/app/renderers/P5Renderer.js +10 -7
- package/src/client/app/renderers/THREERenderer.js +136 -94
- package/src/client/app/stores/audioAnalysis.js +3 -4
- package/src/client/app/stores/console.js +9 -10
- package/src/client/app/stores/errors.js +1 -1
- package/src/client/app/stores/exports.js +36 -20
- package/src/client/app/stores/index.js +2 -2
- package/src/client/app/stores/layout.js +143 -138
- package/src/client/app/stores/multisampling.js +4 -4
- package/src/client/app/stores/props.js +76 -13
- package/src/client/app/stores/renderers.js +26 -15
- package/src/client/app/stores/rendering.js +108 -89
- package/src/client/app/stores/sketches.js +7 -9
- package/src/client/app/stores/time.js +18 -18
- package/src/client/app/stores/utils.js +95 -38
- package/src/client/app/transitions/fade.js +3 -3
- package/src/client/app/transitions/index.js +6 -7
- package/src/client/app/transitions/splitX.js +2 -2
- package/src/client/app/transitions/splitY.js +2 -2
- package/src/client/app/triggers/Keyboard.js +88 -79
- package/src/client/app/triggers/MIDI.js +110 -84
- package/src/client/app/triggers/Mouse.js +73 -65
- package/src/client/app/triggers/Trigger.js +59 -58
- package/src/client/app/triggers/index.js +7 -7
- package/src/client/app/triggers/shared.js +5 -5
- package/src/client/app/ui/Build.svelte +70 -71
- package/src/client/app/ui/ErrorOverlay.svelte +118 -104
- package/src/client/app/ui/Field.svelte +393 -258
- package/src/client/app/ui/FieldGroup.svelte +106 -94
- package/src/client/app/ui/FieldSection.svelte +127 -116
- package/src/client/app/ui/FieldSpace.svelte +29 -30
- package/src/client/app/ui/FieldTrigger.svelte +256 -244
- package/src/client/app/ui/FieldTriggers.svelte +46 -46
- package/src/client/app/ui/FloatingParams.svelte +29 -30
- package/src/client/app/ui/Layout.svelte +31 -32
- package/src/client/app/ui/LayoutColumn.svelte +4 -4
- package/src/client/app/ui/LayoutComponent.svelte +239 -225
- package/src/client/app/ui/LayoutResizer.svelte +195 -176
- package/src/client/app/ui/LayoutRoot.svelte +6 -6
- package/src/client/app/ui/LayoutRow.svelte +4 -4
- package/src/client/app/ui/LayoutToolbar.svelte +191 -194
- package/src/client/app/ui/Module.svelte +134 -135
- package/src/client/app/ui/ModuleHeaderAction.svelte +81 -78
- package/src/client/app/ui/ModuleHeaderButton.svelte +12 -12
- package/src/client/app/ui/ModuleHeaderSelect.svelte +47 -37
- package/src/client/app/ui/ModuleRenderer.svelte +26 -27
- package/src/client/app/ui/OutputRenderer.svelte +112 -105
- package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
- package/src/client/app/ui/ParamsOutput.svelte +130 -113
- package/src/client/app/ui/Preview.svelte +7 -8
- package/src/client/app/ui/SelectChevrons.svelte +27 -15
- package/src/client/app/ui/SketchRenderer.svelte +780 -667
- package/src/client/app/ui/SketchSelect.svelte +50 -44
- package/src/client/app/ui/fields/ButtonInput.svelte +61 -48
- package/src/client/app/ui/fields/CheckboxInput.svelte +67 -61
- package/src/client/app/ui/fields/ColorInput.svelte +294 -238
- package/src/client/app/ui/fields/FieldInputRow.svelte +8 -8
- package/src/client/app/ui/fields/ImageInput.svelte +123 -121
- package/src/client/app/ui/fields/Input.svelte +100 -111
- package/src/client/app/ui/fields/IntervalInput.svelte +268 -0
- package/src/client/app/ui/fields/ListInput.svelte +96 -96
- package/src/client/app/ui/fields/NumberInput.svelte +120 -116
- package/src/client/app/ui/fields/ProgressInput.svelte +99 -73
- package/src/client/app/ui/fields/Select.svelte +137 -124
- package/src/client/app/ui/fields/TextInput.svelte +10 -11
- package/src/client/app/ui/fields/VectorInput.svelte +86 -82
- package/src/client/app/utils/canvas.utils.js +189 -208
- package/src/client/app/utils/color.utils.js +138 -101
- package/src/client/app/utils/fields.utils.js +131 -0
- package/src/client/app/utils/file.utils.js +209 -37
- package/src/client/app/utils/glsl.utils.js +2 -2
- package/src/client/app/utils/glslErrors.js +49 -31
- package/src/client/app/utils/index.js +32 -29
- package/src/client/app/utils/math.utils.js +14 -10
- package/src/client/index.html +16 -16
- package/src/client/main.js +4 -4
- package/src/client/public/css/global.css +26 -16
- package/src/cli/db.js +0 -17
- package/src/cli/index.js +0 -198
- package/src/cli/plugins/db.js +0 -12
- package/src/cli/plugins/hot-shader-reload.js +0 -86
- package/src/cli/plugins/screenshot.js +0 -46
- package/src/cli/server.js +0 -153
- package/src/cli/templates/2d.js +0 -15
- package/src/cli/templates/blank.js +0 -13
- package/src/cli/templates/fragment.js +0 -18
- package/src/cli/templates/index.js +0 -27
- package/src/cli/templates/p5.js +0 -13
- package/src/cli/templates/three-fragment.js +0 -53
- package/src/cli/templates/three-orthographic.js +0 -23
- package/src/cli/templates/three-perspective.js +0 -20
- package/src/client/app/lib/canvas-recorder/FFMPEGRecorder.js +0 -56
- package/src/client/app/utils/props.utils.js +0 -51
- 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/js/ffmpeg.min.js +0 -2
- package/src/client/public/js/ffmpeg.min.js.map +0 -1
- /package/src/cli/templates/{fragment.fs → fragment-gl/fragment.fs} +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import glslify from 'glslify';
|
|
5
|
+
import { log, dim, green, yellow } from '../log.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} ShaderUpdate
|
|
9
|
+
* @property {string} filepath - The path of the shader on the filesystem
|
|
10
|
+
* @property {string} source - The source code of the shader
|
|
11
|
+
* @property {boolean} nohsr - Whether the shader can be injected on the fly or if the sketch needs to be fully reloaded
|
|
12
|
+
* @property {string[]} warnings - Indicates whether the Wisdom component is present.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve shader include directives and send shader code via WebSocket
|
|
17
|
+
* @param {object} params
|
|
18
|
+
* @param {string} [params.cwd=process.cwd()] - Current working directory
|
|
19
|
+
* @param {import('ws').WebSocketServer} params.wss
|
|
20
|
+
* @returns {import('vite').Plugin}
|
|
21
|
+
*/
|
|
22
|
+
export default function hotShaderReplacement({ cwd = process.cwd(), wss }) {
|
|
23
|
+
const name = 'fragment-plugin-hsr';
|
|
24
|
+
const prefix = log.prefix(name);
|
|
25
|
+
const fileRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
|
|
26
|
+
const includeRegex = /#include(\s+([^\s<>]+));?/gi;
|
|
27
|
+
const ignoreRegex = /^(?:\/|\*)*\s*@fragment-nohsr/;
|
|
28
|
+
const commentRegex =
|
|
29
|
+
/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gi;
|
|
30
|
+
const base = process.cwd().split(path.sep).join(path.posix.sep);
|
|
31
|
+
|
|
32
|
+
let dependencies = new Map();
|
|
33
|
+
let shaders = [];
|
|
34
|
+
let modulesToReload = [];
|
|
35
|
+
|
|
36
|
+
function reloadSketch() {
|
|
37
|
+
const clone = [...modulesToReload];
|
|
38
|
+
|
|
39
|
+
modulesToReload = [];
|
|
40
|
+
|
|
41
|
+
if (clone.length > 0) {
|
|
42
|
+
const { file } = clone[0];
|
|
43
|
+
const filepath = path.relative(cwd, file);
|
|
44
|
+
log.message(`${green(`hmr update`)} /${filepath}`);
|
|
45
|
+
|
|
46
|
+
server.ws.send({
|
|
47
|
+
type: 'custom',
|
|
48
|
+
event: 'sketch-update',
|
|
49
|
+
data: {
|
|
50
|
+
file,
|
|
51
|
+
filepath,
|
|
52
|
+
cwd,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return clone;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function addShaderFilepath(shaderSource, shaderPath) {
|
|
61
|
+
let keyword = `void main`;
|
|
62
|
+
let shaderParts = shaderSource.split(keyword);
|
|
63
|
+
let hint = `// <filepath://${shaderPath}>`;
|
|
64
|
+
|
|
65
|
+
return `${shaderParts[0]}
|
|
66
|
+
${hint}
|
|
67
|
+
${keyword}${shaderParts[1]}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getUnixPath(shaderPath) {
|
|
72
|
+
return shaderPath.split(path.sep).join(path.posix.sep);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function compileGLSL(shaderSource, shaderPath) {
|
|
76
|
+
// test if shader source contains hint to avoid shader injection
|
|
77
|
+
const nohsr = ignoreRegex.test(shaderSource);
|
|
78
|
+
|
|
79
|
+
// remove current shader from dependency list before resolving dependencies again
|
|
80
|
+
dependencies.forEach((shadersPaths, dependency) => {
|
|
81
|
+
const shadersList = shadersPaths.filter((p) => p !== shaderPath);
|
|
82
|
+
dependencies.set(dependency, shadersList);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// if a dependency no longer have any parent shader, we can remove it from the watch
|
|
86
|
+
dependencies.forEach((shadersPaths, dependency) => {
|
|
87
|
+
if (shadersPaths.length === 0) {
|
|
88
|
+
if (server) {
|
|
89
|
+
server.watcher.unwatch(dependency);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
dependencies.delete(dependency);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {string} parentSource
|
|
99
|
+
* @param {string} parentPath
|
|
100
|
+
* @param {string[]} deps
|
|
101
|
+
* @returns {}
|
|
102
|
+
*/
|
|
103
|
+
function resolveDependencies(
|
|
104
|
+
parentSource,
|
|
105
|
+
parentPath,
|
|
106
|
+
deps = [],
|
|
107
|
+
warnings = [],
|
|
108
|
+
) {
|
|
109
|
+
// remove comments
|
|
110
|
+
parentSource = parentSource.replace(commentRegex, '');
|
|
111
|
+
|
|
112
|
+
let parentUnixPath = getUnixPath(parentPath);
|
|
113
|
+
let directory = path.dirname(parentUnixPath);
|
|
114
|
+
|
|
115
|
+
if (includeRegex.test(parentSource)) {
|
|
116
|
+
const currentDirectory = directory;
|
|
117
|
+
|
|
118
|
+
parentSource = parentSource.replace(
|
|
119
|
+
includeRegex,
|
|
120
|
+
(_, include) => {
|
|
121
|
+
include = include.trim();
|
|
122
|
+
let chunkPath = include.replace(
|
|
123
|
+
/^(?:"|')?|(?:"|')?;?$/gi,
|
|
124
|
+
'',
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (!chunkPath.indexOf('/')) {
|
|
128
|
+
chunkPath = `${base}/${chunkPath}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const directoryIndex = chunkPath.lastIndexOf('/');
|
|
132
|
+
directory = currentDirectory;
|
|
133
|
+
|
|
134
|
+
if (directoryIndex !== -1) {
|
|
135
|
+
directory = path.resolve(
|
|
136
|
+
directory,
|
|
137
|
+
chunkPath.slice(0, directoryIndex + 1),
|
|
138
|
+
);
|
|
139
|
+
chunkPath = chunkPath.slice(
|
|
140
|
+
directoryIndex + 1,
|
|
141
|
+
chunkPath.length,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let chunkResolvedPath = path.resolve(
|
|
146
|
+
directory,
|
|
147
|
+
chunkPath,
|
|
148
|
+
);
|
|
149
|
+
let extension = 'glsl';
|
|
150
|
+
|
|
151
|
+
if (!path.extname(chunkResolvedPath))
|
|
152
|
+
chunkResolvedPath = `${chunkResolvedPath}.${extension}`;
|
|
153
|
+
|
|
154
|
+
const chunkUnixPath = getUnixPath(chunkResolvedPath);
|
|
155
|
+
|
|
156
|
+
if (!dependencies.has(chunkUnixPath)) {
|
|
157
|
+
// first time chunk is detected
|
|
158
|
+
dependencies.set(chunkUnixPath, []);
|
|
159
|
+
|
|
160
|
+
if (server) {
|
|
161
|
+
server.watcher.add(chunkResolvedPath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const parents = dependencies.get(chunkUnixPath);
|
|
166
|
+
|
|
167
|
+
if (!parents.includes(shaderPath)) {
|
|
168
|
+
parents.push(shaderPath);
|
|
169
|
+
deps.push(chunkResolvedPath);
|
|
170
|
+
} else {
|
|
171
|
+
const message = `Dependency is already included in ${shaderPath}.\nInclude was skipped to avoid errors.`;
|
|
172
|
+
|
|
173
|
+
warnings.push({
|
|
174
|
+
type: 'duplicated dependency',
|
|
175
|
+
message,
|
|
176
|
+
importer: parentPath,
|
|
177
|
+
url: chunkResolvedPath,
|
|
178
|
+
location: {
|
|
179
|
+
lineText: `#include ${include}`,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const chunkSource = fs.readFileSync(
|
|
188
|
+
chunkResolvedPath,
|
|
189
|
+
'utf8',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const { code: chunkCode } = resolveDependencies(
|
|
193
|
+
chunkSource,
|
|
194
|
+
chunkResolvedPath,
|
|
195
|
+
deps,
|
|
196
|
+
warnings,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
const prefix = server
|
|
200
|
+
? `//#include ${include}\n`
|
|
201
|
+
: ``;
|
|
202
|
+
|
|
203
|
+
return `${prefix}\n${chunkCode}`;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error.code === 'ENOENT') {
|
|
206
|
+
warnings.push({
|
|
207
|
+
type: 'not found',
|
|
208
|
+
message: `Cannot find ${chunkResolvedPath}`,
|
|
209
|
+
importer: parentPath,
|
|
210
|
+
url: chunkResolvedPath,
|
|
211
|
+
location: {
|
|
212
|
+
lineText: `#include ${include}`,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return ``;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
code: parentSource.trim().replace(/(\r\n|\r|\n){3,}/g, '$1\n'),
|
|
225
|
+
deps,
|
|
226
|
+
warnings,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let { code, deps, warnings } = resolveDependencies(
|
|
231
|
+
shaderSource,
|
|
232
|
+
shaderPath,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
code = glslify(code, {
|
|
236
|
+
basedir: process.cwd(),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (server) {
|
|
240
|
+
code = addShaderFilepath(code, shaderPath);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
warnings.forEach((warning) => {
|
|
244
|
+
const { location } = warning;
|
|
245
|
+
const line = 1;
|
|
246
|
+
const column = 4;
|
|
247
|
+
log.message(`${yellow(warning.type)} ${warning.importer}`, prefix);
|
|
248
|
+
console.log();
|
|
249
|
+
console.log(` ${dim(location.lineText)}`);
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(warning.message);
|
|
252
|
+
console.log();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return { code, deps, warnings, nohsr };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Reload shaders after changes via custom Websocket or Vite HMR (sketch reload)
|
|
260
|
+
* @param {ShaderUpdate[]} shaderUpdates
|
|
261
|
+
*/
|
|
262
|
+
function reloadShaders(shaderUpdates) {
|
|
263
|
+
const shadersNeedReload = shaderUpdates.filter(
|
|
264
|
+
(shaderUpdate) => shaderUpdate.nohsr,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (shadersNeedReload.length > 0) {
|
|
268
|
+
shadersNeedReload.forEach((shaderUpdate) => {
|
|
269
|
+
log.message(
|
|
270
|
+
`${yellow('hsr ignore')} /${path.relative(cwd, shaderUpdate.filepath)}`,
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return reloadSketch();
|
|
275
|
+
} else {
|
|
276
|
+
shaderUpdates.forEach((shaderUpdate) => {
|
|
277
|
+
log.message(
|
|
278
|
+
`${green('hsr update')} /${path.relative(cwd, shaderUpdate.filepath)}`,
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
wss.send({
|
|
283
|
+
type: 'custom',
|
|
284
|
+
event: 'shader-update',
|
|
285
|
+
data: shaderUpdates,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** @type import('vite').ViteDevServer */
|
|
293
|
+
let server;
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
name: 'fragment-plugin-hsr',
|
|
297
|
+
config: () => ({
|
|
298
|
+
optimizeDeps: {
|
|
299
|
+
esbuildOptions: {
|
|
300
|
+
loader: {
|
|
301
|
+
'.frag': 'text',
|
|
302
|
+
'.vert': 'text',
|
|
303
|
+
'.glsl': 'text',
|
|
304
|
+
'.fs': 'text',
|
|
305
|
+
'.vs': 'text',
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
configureServer(_server) {
|
|
311
|
+
server = _server;
|
|
312
|
+
},
|
|
313
|
+
handleHotUpdate: async ({ modules, file, read }) => {
|
|
314
|
+
const { moduleGraph } = server;
|
|
315
|
+
|
|
316
|
+
if (fileRegex.test(file)) {
|
|
317
|
+
const thisModule = moduleGraph.getModuleById(file);
|
|
318
|
+
|
|
319
|
+
if (thisModule) {
|
|
320
|
+
modulesToReload.push(thisModule);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let unixPath = getUnixPath(file);
|
|
324
|
+
if (shaders.includes(file)) {
|
|
325
|
+
let source = await read();
|
|
326
|
+
let {
|
|
327
|
+
code: glsl,
|
|
328
|
+
warnings,
|
|
329
|
+
nohsr,
|
|
330
|
+
} = compileGLSL(source, file);
|
|
331
|
+
|
|
332
|
+
/** @type ShaderUpdate[] */
|
|
333
|
+
const shaderUpdate = {
|
|
334
|
+
filepath: unixPath,
|
|
335
|
+
source: glsl,
|
|
336
|
+
warnings,
|
|
337
|
+
nohsr,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return reloadShaders([shaderUpdate]);
|
|
341
|
+
} else {
|
|
342
|
+
if (dependencies.has(unixPath)) {
|
|
343
|
+
const shadersList = dependencies.get(unixPath);
|
|
344
|
+
|
|
345
|
+
// retrieve modules from module graph
|
|
346
|
+
const moduleNodes = shadersList.map((moduleNode) =>
|
|
347
|
+
moduleGraph.getModuleById(moduleNode),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// save it as modules to reload to invalidate the top level shaders in case a dependency has been hot updated in between
|
|
351
|
+
modulesToReload.push(...moduleNodes);
|
|
352
|
+
|
|
353
|
+
const sources = await Promise.all(
|
|
354
|
+
shadersList.map((shader) => {
|
|
355
|
+
return readFile(shader, 'utf-8');
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
log.message(
|
|
360
|
+
`${yellow(`dependency update`)} /${path.relative(cwd, unixPath)}`,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
/** @type ShaderUpdate[] */
|
|
364
|
+
const shaderUpdates = shadersList.map(
|
|
365
|
+
(shader, index) => {
|
|
366
|
+
let source = sources[index];
|
|
367
|
+
let {
|
|
368
|
+
code: glsl,
|
|
369
|
+
warnings,
|
|
370
|
+
nohsr,
|
|
371
|
+
} = compileGLSL(source, shader);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
filepath: shader,
|
|
375
|
+
source: glsl,
|
|
376
|
+
warnings: warnings,
|
|
377
|
+
nohsr,
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
return reloadShaders(shaderUpdates);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return [];
|
|
387
|
+
} else if (modulesToReload.length > 0) {
|
|
388
|
+
// only return if some shaders have been updated in between
|
|
389
|
+
// otherwise, returning an empty array would prevent hmr on sketch files
|
|
390
|
+
return reloadSketch();
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
transform(source, file) {
|
|
394
|
+
if (!fileRegex.test(file)) return;
|
|
395
|
+
|
|
396
|
+
if (!shaders.includes(file)) {
|
|
397
|
+
shaders.push(file);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let { code: glsl } = compileGLSL(source, file);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
code: `export default ${JSON.stringify(glsl)}`,
|
|
404
|
+
map: null, // provide source map if available
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import
|
|
3
|
-
import log from "../log.js";
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { log, green } from '../log.js';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Send a custom event to Fragment when a sketch changes
|
|
6
|
+
* @param {object} [params]
|
|
7
|
+
* @param {string} [params.cwd=process.cwd()] - Current working directory
|
|
8
|
+
* @returns {import('vite').Plugin}
|
|
9
|
+
*/
|
|
10
|
+
export default function hotSketchReload({ cwd = process.cwd() } = {}) {
|
|
11
|
+
const shaderRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
|
|
12
|
+
const base = cwd.split(path.sep).join(path.posix.sep);
|
|
13
|
+
return {
|
|
14
|
+
name: 'hot-sketch-reload',
|
|
15
|
+
handleHotUpdate: async ({ server, modules, file }) => {
|
|
16
|
+
if (file.includes(base) && !shaderRegex.test(file)) {
|
|
10
17
|
const filepath = path.relative(cwd, file);
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
|
|
19
|
+
log.message(`${green(`hmr update`)} /${filepath}`);
|
|
20
|
+
|
|
13
21
|
server.ws.send({
|
|
14
22
|
type: 'custom',
|
|
15
23
|
event: 'sketch-update',
|
|
@@ -17,23 +25,11 @@ export default function hotSketchReload({ cwd }) {
|
|
|
17
25
|
file,
|
|
18
26
|
filepath,
|
|
19
27
|
cwd,
|
|
20
|
-
}
|
|
28
|
+
},
|
|
21
29
|
});
|
|
22
|
-
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
return modules;
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
39
35
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import bodyParser from 'body-parser';
|
|
4
|
+
import { log, green, red } from '../log.js';
|
|
5
|
+
import { mkdirp } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {object} [params]
|
|
10
|
+
* @param {string} [cwd=process.cwd()] - Current working directory
|
|
11
|
+
* @param {string} [inlineExportDir] - Directory path used for exports
|
|
12
|
+
* @returns {import('vite').Plugin}
|
|
13
|
+
*/
|
|
14
|
+
export default function screenshot({
|
|
15
|
+
cwd = process.cwd(),
|
|
16
|
+
inlineExportDir,
|
|
17
|
+
} = {}) {
|
|
18
|
+
function resolveDirectory(directoryPath) {
|
|
19
|
+
return path.isAbsolute(directoryPath)
|
|
20
|
+
? directoryPath
|
|
21
|
+
: path.join(cwd, directoryPath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveExportDirectory(exportDir) {
|
|
25
|
+
let directory;
|
|
26
|
+
|
|
27
|
+
if (inlineExportDir) {
|
|
28
|
+
if (!inlineExportDirPath) {
|
|
29
|
+
inlineExportDirPath = resolveDirectory(inlineExportDir);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
directory = inlineExportDirPath;
|
|
33
|
+
|
|
34
|
+
if (exportDir) {
|
|
35
|
+
log.warning(
|
|
36
|
+
`'exportDir' configuration from sketch has been overridden by --exportDir.`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
} else if (exportDir) {
|
|
40
|
+
directory = resolveDirectory(exportDir);
|
|
41
|
+
} else {
|
|
42
|
+
directory = cwd;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return directory;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let inlineExportDirPath;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
name: 'save',
|
|
52
|
+
configureServer(server) {
|
|
53
|
+
server.middlewares.use(bodyParser.json({ limit: '100mb' }));
|
|
54
|
+
server.middlewares.use('/save', async (req, res, next) => {
|
|
55
|
+
if (req.method === 'POST') {
|
|
56
|
+
const { files } = req.body;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const filepaths = [];
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < files.length; i++) {
|
|
62
|
+
const { filename, data, encoding, exportDir } =
|
|
63
|
+
files[i];
|
|
64
|
+
|
|
65
|
+
let directory = resolveExportDirectory(exportDir);
|
|
66
|
+
mkdirp(directory);
|
|
67
|
+
|
|
68
|
+
let filepath = path.join(directory, filename);
|
|
69
|
+
|
|
70
|
+
let buffer = Buffer.from(
|
|
71
|
+
encoding === 'base64'
|
|
72
|
+
? data.split(',')[1]
|
|
73
|
+
: data,
|
|
74
|
+
encoding,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await writeFile(filepath, buffer);
|
|
78
|
+
|
|
79
|
+
log.message(`${green(`export`)} Saved ${filepath}`);
|
|
80
|
+
filepaths.push(filepath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
res.writeHead(200, {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
});
|
|
86
|
+
res.end(JSON.stringify({ filepaths }));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
log.message(`${red(`export`)} Error`);
|
|
89
|
+
console.error(error);
|
|
90
|
+
res.writeHead(500, {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
});
|
|
93
|
+
res.end(JSON.stringify({ error }));
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
next();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { preview as vitePreview } from 'vite';
|
|
4
|
+
import { bold, cyan, log, magenta } from './log.js';
|
|
5
|
+
import * as p from './prompts.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Preview a sketch
|
|
9
|
+
* @param {string} dir
|
|
10
|
+
* @param {object} [options]
|
|
11
|
+
* @param {boolean} [options.open=false]
|
|
12
|
+
*/
|
|
13
|
+
export async function preview(dir, options = {}) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const prefix = log.prefix('preview');
|
|
16
|
+
|
|
17
|
+
const outDir = path.join(cwd, dir);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (!fs.existsSync(outDir)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Directory ${magenta(dir)} does not exist in ${cwd}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
log.message(`${magenta(outDir)}\n`, prefix);
|
|
27
|
+
|
|
28
|
+
const previewServer = await vitePreview({
|
|
29
|
+
build: {
|
|
30
|
+
outDir,
|
|
31
|
+
},
|
|
32
|
+
preview: {
|
|
33
|
+
host: true,
|
|
34
|
+
open: options.open,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const { resolvedUrls } = previewServer;
|
|
39
|
+
|
|
40
|
+
let urls = ``;
|
|
41
|
+
|
|
42
|
+
for (const url of resolvedUrls.local) {
|
|
43
|
+
urls += `${bold('Local')}: ${bold(cyan(url))}\n`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const url of resolvedUrls.network) {
|
|
47
|
+
urls += `${bold('Network')}: ${bold(cyan(url))}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
p.note(urls);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
log.error(`Error\n`, prefix);
|
|
53
|
+
console.error(error);
|
|
54
|
+
}
|
|
55
|
+
}
|