fragment-tools 0.1.13 → 0.1.14
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/bin/index.js +2 -2
- package/package.json +4 -5
- package/src/cli/log.js +31 -21
- package/src/cli/plugins/check-dependencies.js +47 -30
- package/src/cli/plugins/hot-shader-replacement.js +384 -0
- package/src/cli/plugins/hot-sketch-reload.js +3 -13
- package/src/cli/plugins/screenshot.js +57 -20
- package/src/cli/server.js +144 -133
- package/src/client/app/App.svelte +3 -3
- package/src/client/app/client.js +55 -39
- package/src/client/app/components/Init.svelte +12 -9
- package/src/client/app/helpers.js +42 -0
- package/src/client/app/hooks.js +20 -0
- package/src/client/app/inputs/Keyboard.js +13 -15
- package/src/client/app/inputs/MIDI.js +14 -15
- package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +41 -21
- package/src/client/app/lib/gl/Renderer.js +127 -139
- package/src/client/app/modules/Exports.svelte +62 -43
- package/src/client/app/modules/MidiPanel.svelte +100 -101
- package/src/client/app/modules/Params.svelte +116 -103
- package/src/client/app/renderers/2DRenderer.js +3 -3
- package/src/client/app/renderers/FragmentRenderer.js +30 -23
- package/src/client/app/renderers/P5Renderer.js +10 -7
- package/src/client/app/renderers/THREERenderer.js +136 -94
- package/src/client/app/stores/exports.js +36 -20
- package/src/client/app/stores/props.js +28 -5
- package/src/client/app/stores/renderers.js +22 -15
- package/src/client/app/stores/sketches.js +7 -9
- package/src/client/app/stores/utils.js +95 -38
- package/src/client/app/triggers/Keyboard.js +88 -79
- package/src/client/app/triggers/MIDI.js +110 -84
- package/src/client/app/ui/Field.svelte +343 -240
- package/src/client/app/ui/FieldGroup.svelte +106 -94
- package/src/client/app/ui/FieldSection.svelte +125 -116
- package/src/client/app/ui/ParamsMultisampling.svelte +96 -95
- package/src/client/app/ui/ParamsOutput.svelte +113 -113
- package/src/client/app/ui/SelectChevrons.svelte +27 -15
- package/src/client/app/ui/SketchRenderer.svelte +761 -667
- 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/ImageInput.svelte +123 -121
- package/src/client/app/ui/fields/Input.svelte +100 -111
- package/src/client/app/ui/fields/ListInput.svelte +96 -96
- package/src/client/app/ui/fields/NumberInput.svelte +121 -116
- package/src/client/app/ui/fields/ProgressInput.svelte +80 -73
- package/src/client/app/ui/fields/Select.svelte +137 -124
- package/src/client/app/ui/fields/VectorInput.svelte +86 -82
- package/src/client/app/utils/canvas.utils.js +228 -201
- package/src/client/app/utils/file.utils.js +38 -34
- package/src/client/public/css/global.css +27 -21
- package/src/cli/plugins/hot-shader-reload.js +0 -86
package/bin/index.js
CHANGED
|
@@ -4,14 +4,14 @@ import sade from 'sade';
|
|
|
4
4
|
import { run } from '../src/cli/index.js';
|
|
5
5
|
|
|
6
6
|
sade('fragment [entry]')
|
|
7
|
-
.version('0.1.
|
|
7
|
+
.version('0.1.14')
|
|
8
8
|
.describe('Run a dev environment for fragment')
|
|
9
9
|
.option('-n, --new', 'Create file if it does not exist', false)
|
|
10
10
|
.option('-t, --template', 'Specify template to create the file from', '2d')
|
|
11
11
|
.option('-p, --port', 'Port to bind', 3000)
|
|
12
12
|
.option('-dev, --development', 'Enable development mode', false)
|
|
13
13
|
.option('-b, --build', 'Build sketch for production', false)
|
|
14
|
-
.option('--exportDir', 'Directory used for exports'
|
|
14
|
+
.option('--exportDir', 'Directory used for exports')
|
|
15
15
|
.option('--outDir', 'Directory used for static build', null)
|
|
16
16
|
.option('--emptyOutDir', 'Empty outDir before static build', false)
|
|
17
17
|
.action((entry, options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fragment-tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "A web development environment for creative coding",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://github.com/raphaelameaume/fragment#readme",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@
|
|
22
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.2",
|
|
21
|
+
"@sveltejs/vite-plugin-svelte": "^2.4.5",
|
|
23
22
|
"body-parser": "^1.20.0",
|
|
24
23
|
"changedpi": "^1.0.4",
|
|
25
24
|
"chokidar": "^3.5.2",
|
|
@@ -29,9 +28,9 @@
|
|
|
29
28
|
"glslify": "^7.1.1",
|
|
30
29
|
"kleur": "^4.1.4",
|
|
31
30
|
"sade": "^1.7.4",
|
|
32
|
-
"svelte": "^
|
|
31
|
+
"svelte": "^4.0.0",
|
|
33
32
|
"svelte-json-tree": "^1.0.0",
|
|
34
|
-
"vite": "^
|
|
33
|
+
"vite": "^4.4.9",
|
|
35
34
|
"webm-writer": "^1.0.0",
|
|
36
35
|
"ws": "^8.2.3"
|
|
37
36
|
}
|
package/src/cli/log.js
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import kleur from
|
|
1
|
+
import kleur from 'kleur';
|
|
2
2
|
|
|
3
3
|
const log = (() => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
4
|
+
let _prefix = createPrefix('fragment');
|
|
5
|
+
|
|
6
|
+
const success = (message, prefix = _prefix) => {
|
|
7
|
+
console.log(prefix, kleur.green(`✔ ${message}`));
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const warning = (message, prefix = _prefix) => {
|
|
11
|
+
console.log(prefix, kleur.yellow(`ℹ ${message}`));
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const error = (message, prefix = _prefix) => {
|
|
15
|
+
console.log(prefix, kleur.red(`✖ ${message}`));
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const text = (message, prefix = _prefix) => {
|
|
19
|
+
console.log(prefix, message);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function createPrefix(prefix) {
|
|
23
|
+
return kleur.grey(`[${prefix}]`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
prefix: _prefix,
|
|
28
|
+
createPrefix,
|
|
29
|
+
success,
|
|
30
|
+
warning,
|
|
31
|
+
error,
|
|
32
|
+
text,
|
|
33
|
+
};
|
|
24
34
|
})();
|
|
25
35
|
|
|
26
36
|
export default log;
|
|
@@ -1,36 +1,45 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import fs from
|
|
3
|
-
import log from
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import log from '../log.js';
|
|
4
4
|
|
|
5
|
-
export default function checkDependencies({
|
|
6
|
-
|
|
5
|
+
export default function checkDependencies({
|
|
6
|
+
cwd,
|
|
7
|
+
app,
|
|
8
|
+
entriesPaths,
|
|
9
|
+
build,
|
|
10
|
+
} = {}) {
|
|
11
|
+
const regex =
|
|
12
|
+
/\bexport[\s]*\b(let|const)[\s]*\brendering\b[\s]*=[\s]*["'](.*?)["']/;
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const dependenciesMap = new Map();
|
|
15
|
+
dependenciesMap.set('three', ['three']);
|
|
16
|
+
dependenciesMap.set('p5', ['p5']);
|
|
17
|
+
dependenciesMap.set('fragment', []);
|
|
18
|
+
dependenciesMap.set('2d', []);
|
|
13
19
|
|
|
14
20
|
const renderers = new Map();
|
|
15
|
-
renderers.set(
|
|
16
|
-
renderers.set(
|
|
17
|
-
renderers.set(
|
|
18
|
-
renderers.set(
|
|
19
|
-
renderers.set(
|
|
21
|
+
renderers.set('three', `${app}/renderers/THREERenderer.js`);
|
|
22
|
+
renderers.set('p5', `${app}/renderers/P5Renderer.js`);
|
|
23
|
+
renderers.set('ogl', `${app}/renderers/OGLRenderer.js`);
|
|
24
|
+
renderers.set('fragment', `${app}/renderers/FragmentRenderer.js`);
|
|
25
|
+
renderers.set('2d', `${app}/renderers/2DRenderer.js`);
|
|
20
26
|
|
|
21
27
|
const renderings = [];
|
|
22
28
|
|
|
23
29
|
entriesPaths.forEach((entry) => {
|
|
24
|
-
const content = fs.readFileSync(entry, { encoding:
|
|
25
|
-
|
|
30
|
+
const content = fs.readFileSync(entry, { encoding: 'utf-8' });
|
|
31
|
+
const match = content.match(regex);
|
|
26
32
|
const rendering = match && match[2];
|
|
27
33
|
|
|
28
|
-
if (rendering) {
|
|
34
|
+
if (rendering && dependenciesMap.has(rendering)) {
|
|
29
35
|
renderings.push(rendering);
|
|
30
36
|
|
|
31
37
|
const dependencies = dependenciesMap.get(rendering);
|
|
32
38
|
dependencies.forEach((dependency) => {
|
|
33
|
-
const dependencyPath = path.join(
|
|
39
|
+
const dependencyPath = path.join(
|
|
40
|
+
cwd,
|
|
41
|
+
`node_modules/${dependency}`,
|
|
42
|
+
);
|
|
34
43
|
const isInstalled = fs.existsSync(dependencyPath);
|
|
35
44
|
|
|
36
45
|
if (!isInstalled) {
|
|
@@ -40,7 +49,7 @@ export default function checkDependencies({ cwd, app, entriesPaths, build } = {}
|
|
|
40
49
|
console.log(`
|
|
41
50
|
It looks like you're trying to build a ${dependency} sketch (${filename}) without having ${dependency} installed.
|
|
42
51
|
Run 'npm install ${dependency}' before starting Fragment to run ${filename}.
|
|
43
|
-
`)
|
|
52
|
+
`);
|
|
44
53
|
|
|
45
54
|
throw new Error(error);
|
|
46
55
|
}
|
|
@@ -58,20 +67,28 @@ Run 'npm install ${dependency}' before starting Fragment to run ${filename}.
|
|
|
58
67
|
})
|
|
59
68
|
.flat();
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
return {
|
|
71
|
+
name: 'check-dependencies',
|
|
63
72
|
config: () => ({
|
|
64
73
|
define: {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
__THREE_RENDERER__: build
|
|
75
|
+
? renderings.some((rendering) => rendering === 'three')
|
|
76
|
+
: true,
|
|
77
|
+
__P5_RENDERER__: build
|
|
78
|
+
? renderings.some((rendering) => rendering === 'p5')
|
|
79
|
+
: true,
|
|
80
|
+
__FRAGMENT_RENDERER__: build
|
|
81
|
+
? renderings.some((rendering) => rendering === 'fragment')
|
|
82
|
+
: true,
|
|
83
|
+
__2D_RENDERER__: build
|
|
84
|
+
? renderings.some((rendering) => rendering === '2d')
|
|
85
|
+
: true,
|
|
69
86
|
},
|
|
70
87
|
}),
|
|
71
|
-
|
|
88
|
+
load(id) {
|
|
72
89
|
if (build && skipFiles.includes(id)) {
|
|
73
|
-
return { code:
|
|
90
|
+
return { code: '', map: null };
|
|
74
91
|
}
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
},
|
|
93
|
+
};
|
|
77
94
|
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { posix, sep, resolve, dirname, extname, relative } from 'path';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import glslify from 'glslify';
|
|
4
|
+
import log from '../log.js';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
import kleur from 'kleur';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} ShaderUpdate
|
|
10
|
+
* @property {string} filepath - The path of the shader on the filesystem
|
|
11
|
+
* @property {string} source - The source code of the shader
|
|
12
|
+
* @property {boolean} nohsr - Whether the shader can be injected on the fly or if the sketch needs to be fully reloaded
|
|
13
|
+
* @property {string[]} warnings - Indicates whether the Wisdom component is present.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default function hotShaderReplacement({ cwd, wss, watch = false }) {
|
|
17
|
+
const name = 'fragment-plugin-hsr';
|
|
18
|
+
const prefix = log.createPrefix(name);
|
|
19
|
+
const fileRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
|
|
20
|
+
const includeRegex = /#include(\s+([^\s<>]+));?/gi;
|
|
21
|
+
const ignoreRegex = /^(?:\/|\*)*\s*@fragment-nohsr/;
|
|
22
|
+
const commentRegex =
|
|
23
|
+
/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/gi;
|
|
24
|
+
const base = process.cwd().split(sep).join(posix.sep);
|
|
25
|
+
|
|
26
|
+
let dependencies = new Map();
|
|
27
|
+
let shaders = [];
|
|
28
|
+
let modulesToReload = [];
|
|
29
|
+
|
|
30
|
+
function reloadSketch() {
|
|
31
|
+
const clone = [...modulesToReload];
|
|
32
|
+
|
|
33
|
+
modulesToReload = [];
|
|
34
|
+
|
|
35
|
+
if (clone.length > 0) {
|
|
36
|
+
const { file } = clone[0];
|
|
37
|
+
const filepath = relative(cwd, file);
|
|
38
|
+
console.log(
|
|
39
|
+
`${log.prefix} ${kleur.green(`hmr update`)} /${filepath}`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
server.ws.send({
|
|
43
|
+
type: 'custom',
|
|
44
|
+
event: 'sketch-update',
|
|
45
|
+
data: {
|
|
46
|
+
file,
|
|
47
|
+
filepath,
|
|
48
|
+
cwd,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return clone;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function addShaderFilepath(shaderSource, shaderPath) {
|
|
57
|
+
let keyword = `void main`;
|
|
58
|
+
let shaderParts = shaderSource.split(keyword);
|
|
59
|
+
let hint = `// <filepath://${shaderPath}>`;
|
|
60
|
+
|
|
61
|
+
return `${shaderParts[0]}
|
|
62
|
+
${hint}
|
|
63
|
+
${keyword}${shaderParts[1]}
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getUnixPath(shaderPath) {
|
|
68
|
+
return shaderPath.split(sep).join(posix.sep);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function compileGLSL(shaderSource, shaderPath) {
|
|
72
|
+
// test if shader source contains hint to avoid shader injection
|
|
73
|
+
const nohsr = ignoreRegex.test(shaderSource);
|
|
74
|
+
|
|
75
|
+
// remove current shader from dependency list before resolving dependencies again
|
|
76
|
+
dependencies.forEach((shadersPaths, dependency) => {
|
|
77
|
+
const shadersList = shadersPaths.filter((p) => p !== shaderPath);
|
|
78
|
+
dependencies.set(dependency, shadersList);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// if a dependency no longer have any parent shader, we can remove it from the watch
|
|
82
|
+
dependencies.forEach((shadersPaths, dependency) => {
|
|
83
|
+
if (shadersPaths.length === 0) {
|
|
84
|
+
if (server) {
|
|
85
|
+
server.watcher.unwatch(dependency);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
dependencies.delete(dependency);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @param {string} parentSource
|
|
95
|
+
* @param {string} parentPath
|
|
96
|
+
* @param {string[]} deps
|
|
97
|
+
* @returns {}
|
|
98
|
+
*/
|
|
99
|
+
function resolveDependencies(
|
|
100
|
+
parentSource,
|
|
101
|
+
parentPath,
|
|
102
|
+
deps = [],
|
|
103
|
+
warnings = [],
|
|
104
|
+
) {
|
|
105
|
+
// remove comments
|
|
106
|
+
parentSource = parentSource.replace(commentRegex, '');
|
|
107
|
+
|
|
108
|
+
let parentUnixPath = getUnixPath(parentPath);
|
|
109
|
+
let directory = dirname(parentUnixPath);
|
|
110
|
+
|
|
111
|
+
if (includeRegex.test(parentSource)) {
|
|
112
|
+
const currentDirectory = directory;
|
|
113
|
+
|
|
114
|
+
parentSource = parentSource.replace(
|
|
115
|
+
includeRegex,
|
|
116
|
+
(_, include) => {
|
|
117
|
+
include = include.trim();
|
|
118
|
+
let chunkPath = include.replace(
|
|
119
|
+
/^(?:"|')?|(?:"|')?;?$/gi,
|
|
120
|
+
'',
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!chunkPath.indexOf('/')) {
|
|
124
|
+
chunkPath = `${base}/${chunkPath}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const directoryIndex = chunkPath.lastIndexOf('/');
|
|
128
|
+
directory = currentDirectory;
|
|
129
|
+
|
|
130
|
+
if (directoryIndex !== -1) {
|
|
131
|
+
directory = resolve(
|
|
132
|
+
directory,
|
|
133
|
+
chunkPath.slice(0, directoryIndex + 1),
|
|
134
|
+
);
|
|
135
|
+
chunkPath = chunkPath.slice(
|
|
136
|
+
directoryIndex + 1,
|
|
137
|
+
chunkPath.length,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let chunkResolvedPath = resolve(directory, chunkPath);
|
|
142
|
+
let extension = 'glsl';
|
|
143
|
+
|
|
144
|
+
if (!extname(chunkResolvedPath))
|
|
145
|
+
chunkResolvedPath = `${chunkResolvedPath}.${extension}`;
|
|
146
|
+
|
|
147
|
+
const chunkUnixPath = getUnixPath(chunkResolvedPath);
|
|
148
|
+
|
|
149
|
+
if (!dependencies.has(chunkUnixPath)) {
|
|
150
|
+
// first time chunk is detected
|
|
151
|
+
dependencies.set(chunkUnixPath, []);
|
|
152
|
+
|
|
153
|
+
if (server) {
|
|
154
|
+
server.watcher.add(chunkResolvedPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const parents = dependencies.get(chunkUnixPath);
|
|
159
|
+
|
|
160
|
+
if (!parents.includes(shaderPath)) {
|
|
161
|
+
parents.push(shaderPath);
|
|
162
|
+
deps.push(chunkResolvedPath);
|
|
163
|
+
} else {
|
|
164
|
+
const message = `Dependency is already included in ${shaderPath}.\nInclude was skipped to avoid errors.`;
|
|
165
|
+
|
|
166
|
+
warnings.push({
|
|
167
|
+
type: 'duplicated dependency',
|
|
168
|
+
message,
|
|
169
|
+
importer: parentPath,
|
|
170
|
+
url: chunkResolvedPath,
|
|
171
|
+
location: {
|
|
172
|
+
lineText: `#include ${include}`,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { code: chunkCode } = resolveDependencies(
|
|
180
|
+
readFileSync(chunkResolvedPath, 'utf8'),
|
|
181
|
+
chunkResolvedPath,
|
|
182
|
+
deps,
|
|
183
|
+
warnings,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const prefix = server ? `//#include ${include}\n` : ``;
|
|
187
|
+
|
|
188
|
+
return `${prefix}\n${chunkCode}`;
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
code: parentSource.trim().replace(/(\r\n|\r|\n){3,}/g, '$1\n'),
|
|
195
|
+
deps,
|
|
196
|
+
warnings,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let { code, deps, warnings } = resolveDependencies(
|
|
201
|
+
shaderSource,
|
|
202
|
+
shaderPath,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
code = glslify(code, {
|
|
206
|
+
basedir: process.cwd(),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
if (server) {
|
|
210
|
+
code = addShaderFilepath(code, shaderPath);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
warnings.forEach((warning) => {
|
|
214
|
+
const { location } = warning;
|
|
215
|
+
const line = 1;
|
|
216
|
+
const column = 4;
|
|
217
|
+
log.text(
|
|
218
|
+
`${kleur.yellow(warning.type)} ${warning.importer}`,
|
|
219
|
+
prefix,
|
|
220
|
+
);
|
|
221
|
+
console.log();
|
|
222
|
+
console.log(` ${kleur.dim(location.lineText)}`);
|
|
223
|
+
console.log();
|
|
224
|
+
console.log(warning.message);
|
|
225
|
+
console.log();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return { code, deps, warnings, nohsr };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Reload shaders after changes via custom Websocket or Vite HMR (sketch reload)
|
|
233
|
+
* @param {ShaderUpdate[]} shaderUpdates
|
|
234
|
+
*/
|
|
235
|
+
function reloadShaders(shaderUpdates) {
|
|
236
|
+
const shadersNeedReload = shaderUpdates.filter(
|
|
237
|
+
(shaderUpdate) => shaderUpdate.nohsr,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (shadersNeedReload.length > 0) {
|
|
241
|
+
shadersNeedReload.forEach((shaderUpdate) => {
|
|
242
|
+
log.text(
|
|
243
|
+
`${kleur.yellow('hsr ignore')} ${shaderUpdate.filepath}`,
|
|
244
|
+
prefix,
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return reloadSketch();
|
|
249
|
+
} else {
|
|
250
|
+
shaderUpdates.forEach((shaderUpdate) => {
|
|
251
|
+
log.text(
|
|
252
|
+
`${kleur.green('hsr update')} ${shaderUpdate.filepath}`,
|
|
253
|
+
prefix,
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
wss.send({
|
|
258
|
+
type: 'custom',
|
|
259
|
+
event: 'shader-update',
|
|
260
|
+
data: shaderUpdates,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** @type import('vite').ViteDevServer */
|
|
268
|
+
let server;
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
name: 'fragment-plugin-hsr',
|
|
272
|
+
config: () => ({
|
|
273
|
+
optimizeDeps: {
|
|
274
|
+
esbuildOptions: {
|
|
275
|
+
loader: {
|
|
276
|
+
'.frag': 'text',
|
|
277
|
+
'.vert': 'text',
|
|
278
|
+
'.glsl': 'text',
|
|
279
|
+
'.fs': 'text',
|
|
280
|
+
'.vs': 'text',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
}),
|
|
285
|
+
configureServer(_server) {
|
|
286
|
+
server = _server;
|
|
287
|
+
},
|
|
288
|
+
handleHotUpdate: async ({ modules, file, read }) => {
|
|
289
|
+
const { moduleGraph } = server;
|
|
290
|
+
|
|
291
|
+
if (fileRegex.test(file)) {
|
|
292
|
+
const thisModule = moduleGraph.getModuleById(file);
|
|
293
|
+
|
|
294
|
+
if (thisModule) {
|
|
295
|
+
modulesToReload.push(thisModule);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let unixPath = getUnixPath(file);
|
|
299
|
+
if (shaders.includes(file)) {
|
|
300
|
+
let source = await read();
|
|
301
|
+
let {
|
|
302
|
+
code: glsl,
|
|
303
|
+
warnings,
|
|
304
|
+
nohsr,
|
|
305
|
+
} = compileGLSL(source, file);
|
|
306
|
+
|
|
307
|
+
/** @type ShaderUpdate[] */
|
|
308
|
+
const shaderUpdate = {
|
|
309
|
+
filepath: unixPath,
|
|
310
|
+
source: glsl,
|
|
311
|
+
warnings,
|
|
312
|
+
nohsr,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return reloadShaders([shaderUpdate]);
|
|
316
|
+
} else {
|
|
317
|
+
if (dependencies.has(unixPath)) {
|
|
318
|
+
const shadersList = dependencies.get(unixPath);
|
|
319
|
+
|
|
320
|
+
// retrieve modules from module graph
|
|
321
|
+
const moduleNodes = shadersList.map((moduleNode) =>
|
|
322
|
+
moduleGraph.getModuleById(moduleNode),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// save it as modules to reload to invalidate the top level shaders in case a dependency has been hot updated in between
|
|
326
|
+
modulesToReload.push(...moduleNodes);
|
|
327
|
+
|
|
328
|
+
const sources = await Promise.all(
|
|
329
|
+
shadersList.map((shader) => {
|
|
330
|
+
return readFile(shader, 'utf-8');
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
log.text(
|
|
335
|
+
`${kleur.yellow(`dependency update`)} ${unixPath}`,
|
|
336
|
+
prefix,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
/** @type ShaderUpdate[] */
|
|
340
|
+
const shaderUpdates = shadersList.map(
|
|
341
|
+
(shader, index) => {
|
|
342
|
+
let source = sources[index];
|
|
343
|
+
let {
|
|
344
|
+
code: glsl,
|
|
345
|
+
warnings,
|
|
346
|
+
nohsr,
|
|
347
|
+
} = compileGLSL(source, shader);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
filepath: shader,
|
|
351
|
+
source: glsl,
|
|
352
|
+
warnings: warnings,
|
|
353
|
+
nohsr,
|
|
354
|
+
};
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
return reloadShaders(shaderUpdates);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return [];
|
|
363
|
+
} else if (modulesToReload.length > 0) {
|
|
364
|
+
// only return if some shaders have been updated in between
|
|
365
|
+
// otherwise, returning an empty array would prevent hmr on sketch files
|
|
366
|
+
return reloadSketch();
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
transform(source, file) {
|
|
370
|
+
if (!fileRegex.test(file)) return;
|
|
371
|
+
|
|
372
|
+
if (!shaders.includes(file)) {
|
|
373
|
+
shaders.push(file);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let { code: glsl } = compileGLSL(source, file);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
code: `export default ${JSON.stringify(glsl)}`,
|
|
380
|
+
map: null, // provide source map if available
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -3,10 +3,11 @@ import kleur from "kleur";
|
|
|
3
3
|
import log from "../log.js";
|
|
4
4
|
|
|
5
5
|
export default function hotSketchReload({ cwd }) {
|
|
6
|
+
const shaderRegex = /\.(?:frag|vert|glsl|vs|fs)$/;
|
|
6
7
|
return {
|
|
7
8
|
name: 'hot-sketch-reload',
|
|
8
9
|
handleHotUpdate: async ({ server, modules, file, read }) => {
|
|
9
|
-
if (file.includes(cwd)) {
|
|
10
|
+
if (file.includes(cwd) && !shaderRegex.test(file)) {
|
|
10
11
|
const filepath = path.relative(cwd, file);
|
|
11
12
|
console.log(`${log.prefix} ${kleur.green(`hmr update`)} /${filepath}`);
|
|
12
13
|
|
|
@@ -23,17 +24,6 @@ export default function hotSketchReload({ cwd }) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
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
|
-
// }
|
|
27
|
+
}
|
|
38
28
|
};
|
|
39
29
|
}
|