devjar 0.7.0 → 0.8.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 +85 -12
- package/dist/gl.js +214 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
# devjar
|
|
2
2
|
> live code runtime for your react project in browser
|
|
3
3
|
|
|
4
|
+

|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Introduction
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
devjar is a library that enables you to live test and share your code snippets and examples with others. devjar will generate a live code editor where you can run your code snippets and view the results in real-time based on the provided code content of your React app.
|
|
8
9
|
|
|
9
|
-
devjar
|
|
10
|
+
**Notice:** devjar only works for browser runtime at the moment. It will always render the default export component in `index.js` as the app entry.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
### Install
|
|
12
|
+
## Install
|
|
14
13
|
|
|
15
14
|
```sh
|
|
16
15
|
pnpm add devjar
|
|
17
16
|
```
|
|
18
17
|
|
|
18
|
+
## React Code Runtime
|
|
19
19
|
|
|
20
|
-
###
|
|
21
|
-
|
|
22
|
-
#### `<DevJar>`
|
|
20
|
+
### `<DevJar>`
|
|
23
21
|
|
|
24
22
|
`DevJar` is a react component that allows you to develop and test your code directly in the browser, using a CDN to load your dependencies.
|
|
25
23
|
|
|
@@ -29,6 +27,7 @@ pnpm add devjar
|
|
|
29
27
|
* `getModuleUrl`: A function that maps module names to CDN URLs.
|
|
30
28
|
* `onError`: Callback function of error event from the iframe sandbox. By default `console.log`.
|
|
31
29
|
|
|
30
|
+
**Example**
|
|
32
31
|
|
|
33
32
|
```jsx
|
|
34
33
|
import { DevJar } from 'devjar'
|
|
@@ -51,7 +50,9 @@ function App() {
|
|
|
51
50
|
}
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
### `useLiveCode(options)`
|
|
54
|
+
|
|
55
|
+
A hook that provides lower-level control over the live code execution environment.
|
|
55
56
|
|
|
56
57
|
**Parameters**
|
|
57
58
|
|
|
@@ -65,6 +66,8 @@ function App() {
|
|
|
65
66
|
* `error`: An error message in case the live coding encounters an issue.
|
|
66
67
|
* `load(codeFiles)`: void: Loads code files and executes them as live code.
|
|
67
68
|
|
|
69
|
+
**Example**
|
|
70
|
+
|
|
68
71
|
```jsx
|
|
69
72
|
import { useLiveCode } from 'devjar'
|
|
70
73
|
|
|
@@ -103,7 +106,77 @@ function Playground() {
|
|
|
103
106
|
}
|
|
104
107
|
```
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
## GLSL Shader Runtime
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
### `useGL(options)`
|
|
112
|
+
|
|
113
|
+
A hook that renders GLSL fragment shaders using WebGL. Perfect for creating interactive shader playgrounds and visualizations.
|
|
114
|
+
|
|
115
|
+
**Parameters**
|
|
116
|
+
|
|
117
|
+
* `options`
|
|
118
|
+
* `fragment`: The GLSL fragment shader source code as a string.
|
|
119
|
+
* `canvasRef`: A React ref to an HTML canvas element where the shader will be rendered.
|
|
120
|
+
* `onError`: Optional callback function that receives error messages (prefixed with `devjar:gl`).
|
|
121
|
+
|
|
122
|
+
**Available Uniforms**
|
|
123
|
+
|
|
124
|
+
The hook automatically provides these uniforms to your fragment shader:
|
|
125
|
+
|
|
126
|
+
* `u_time`: `float` - Elapsed time in seconds since the renderer started
|
|
127
|
+
* `u_resolution`: `vec2` - Canvas dimensions (width, height)
|
|
128
|
+
* `u_mouse`: `vec2` - Normalized mouse position (0.0 to 1.0)
|
|
129
|
+
|
|
130
|
+
**Example**
|
|
131
|
+
|
|
132
|
+
```jsx
|
|
133
|
+
import { useGL } from 'devjar'
|
|
134
|
+
import { useRef, useState } from 'react'
|
|
135
|
+
|
|
136
|
+
function ShaderPlayground() {
|
|
137
|
+
const canvasRef = useRef(null)
|
|
138
|
+
const [shaderCode, setShaderCode] = useState(`
|
|
139
|
+
precision mediump float;
|
|
140
|
+
|
|
141
|
+
uniform vec2 u_resolution;
|
|
142
|
+
uniform float u_time;
|
|
143
|
+
uniform vec2 u_mouse;
|
|
144
|
+
|
|
145
|
+
void main() {
|
|
146
|
+
vec2 st = gl_FragCoord.xy / u_resolution.xy;
|
|
147
|
+
vec3 color = vec3(st.x, st.y, abs(sin(u_time)));
|
|
148
|
+
gl_FragColor = vec4(color, 1.0);
|
|
149
|
+
}
|
|
150
|
+
`)
|
|
151
|
+
const [error, setError] = useState(null)
|
|
152
|
+
|
|
153
|
+
useGL({
|
|
154
|
+
fragment: shaderCode,
|
|
155
|
+
canvasRef,
|
|
156
|
+
onError: setError
|
|
157
|
+
})
|
|
109
158
|
|
|
159
|
+
return (
|
|
160
|
+
<div>
|
|
161
|
+
<textarea
|
|
162
|
+
value={shaderCode}
|
|
163
|
+
onChange={(e) => setShaderCode(e.target.value)}
|
|
164
|
+
style={{ width: '100%', height: '200px' }}
|
|
165
|
+
/>
|
|
166
|
+
<canvas ref={canvasRef} style={{ width: '100%', height: '300px' }} />
|
|
167
|
+
{error && <pre style={{ color: 'red' }}>{error}</pre>}
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Error Handling**
|
|
174
|
+
|
|
175
|
+
All errors are prefixed with `devjar:gl` for easy identification:
|
|
176
|
+
- `devjar:gl Shader compilation error: ...`
|
|
177
|
+
- `devjar:gl Program linking error: ...`
|
|
178
|
+
- `devjar:gl WebGL is not supported in your browser`
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
The MIT License (MIT).
|
package/dist/gl.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// Default vertex shader - simple fullscreen quad
|
|
4
|
+
const DEFAULT_VERTEX_SHADER = `\
|
|
5
|
+
attribute vec2 a_position;
|
|
6
|
+
|
|
7
|
+
void main() {
|
|
8
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
function createShader(gl, type, source) {
|
|
12
|
+
const shader = gl.createShader(type);
|
|
13
|
+
if (!shader) return null;
|
|
14
|
+
gl.shaderSource(shader, source);
|
|
15
|
+
gl.compileShader(shader);
|
|
16
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
17
|
+
const error = gl.getShaderInfoLog(shader);
|
|
18
|
+
gl.deleteShader(shader);
|
|
19
|
+
throw new Error(`devjar:gl Shader compilation error: ${error}`);
|
|
20
|
+
}
|
|
21
|
+
return shader;
|
|
22
|
+
}
|
|
23
|
+
function createProgram(gl, vertexShader, fragmentShader) {
|
|
24
|
+
const program = gl.createProgram();
|
|
25
|
+
if (!program) return null;
|
|
26
|
+
gl.attachShader(program, vertexShader);
|
|
27
|
+
gl.attachShader(program, fragmentShader);
|
|
28
|
+
gl.linkProgram(program);
|
|
29
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
30
|
+
const error = gl.getProgramInfoLog(program);
|
|
31
|
+
gl.deleteProgram(program);
|
|
32
|
+
throw new Error(`devjar:gl Program linking error: ${error}`);
|
|
33
|
+
}
|
|
34
|
+
return program;
|
|
35
|
+
}
|
|
36
|
+
function createQuad(gl) {
|
|
37
|
+
const buffer = gl.createBuffer();
|
|
38
|
+
if (!buffer) return null;
|
|
39
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
40
|
+
// Fullscreen quad: two triangles covering the entire screen
|
|
41
|
+
const positions = new Float32Array([
|
|
42
|
+
-1,
|
|
43
|
+
-1,
|
|
44
|
+
1,
|
|
45
|
+
-1,
|
|
46
|
+
-1,
|
|
47
|
+
1,
|
|
48
|
+
-1,
|
|
49
|
+
1,
|
|
50
|
+
1,
|
|
51
|
+
-1,
|
|
52
|
+
1,
|
|
53
|
+
1
|
|
54
|
+
]);
|
|
55
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
|
56
|
+
return buffer;
|
|
57
|
+
}
|
|
58
|
+
function useGL({ fragment, canvasRef, onError }) {
|
|
59
|
+
const animationFrameRef = useRef(undefined);
|
|
60
|
+
const glRef = useRef(null);
|
|
61
|
+
const programRef = useRef(null);
|
|
62
|
+
const positionBufferRef = useRef(null);
|
|
63
|
+
const startTimeRef = useRef(performance.now());
|
|
64
|
+
const mouseRef = useRef({
|
|
65
|
+
x: 0,
|
|
66
|
+
y: 0
|
|
67
|
+
});
|
|
68
|
+
// Initialize WebGL
|
|
69
|
+
useEffect(()=>{
|
|
70
|
+
const canvas = canvasRef.current;
|
|
71
|
+
if (!canvas) return;
|
|
72
|
+
const gl = canvas.getContext('webgl');
|
|
73
|
+
if (!gl) {
|
|
74
|
+
onError?.('devjar:gl WebGL is not supported in your browser');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
glRef.current = gl;
|
|
78
|
+
// Create quad buffer once
|
|
79
|
+
const positionBuffer = createQuad(gl);
|
|
80
|
+
if (positionBuffer) {
|
|
81
|
+
positionBufferRef.current = positionBuffer;
|
|
82
|
+
}
|
|
83
|
+
// Set canvas size
|
|
84
|
+
const resizeCanvas = ()=>{
|
|
85
|
+
const rect = canvas.getBoundingClientRect();
|
|
86
|
+
const dpr = window.devicePixelRatio || 1;
|
|
87
|
+
const width = rect.width;
|
|
88
|
+
const height = rect.height;
|
|
89
|
+
canvas.width = width * dpr;
|
|
90
|
+
canvas.height = height * dpr;
|
|
91
|
+
canvas.style.width = `${width}px`;
|
|
92
|
+
canvas.style.height = `${height}px`;
|
|
93
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
94
|
+
};
|
|
95
|
+
resizeCanvas();
|
|
96
|
+
window.addEventListener('resize', resizeCanvas);
|
|
97
|
+
// Use ResizeObserver for more accurate sizing
|
|
98
|
+
const resizeObserver = new ResizeObserver(()=>{
|
|
99
|
+
resizeCanvas();
|
|
100
|
+
});
|
|
101
|
+
resizeObserver.observe(canvas);
|
|
102
|
+
// Handle mouse movement
|
|
103
|
+
const handleMouseMove = (e)=>{
|
|
104
|
+
const rect = canvas.getBoundingClientRect();
|
|
105
|
+
mouseRef.current = {
|
|
106
|
+
x: (e.clientX - rect.left) / rect.width,
|
|
107
|
+
y: 1.0 - (e.clientY - rect.top) / rect.height
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
canvas.addEventListener('mousemove', handleMouseMove);
|
|
111
|
+
return ()=>{
|
|
112
|
+
window.removeEventListener('resize', resizeCanvas);
|
|
113
|
+
canvas.removeEventListener('mousemove', handleMouseMove);
|
|
114
|
+
resizeObserver.disconnect();
|
|
115
|
+
if (animationFrameRef.current) {
|
|
116
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
117
|
+
}
|
|
118
|
+
if (positionBufferRef.current && gl) {
|
|
119
|
+
gl.deleteBuffer(positionBufferRef.current);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}, [
|
|
123
|
+
canvasRef,
|
|
124
|
+
onError
|
|
125
|
+
]);
|
|
126
|
+
// Compile and render shader
|
|
127
|
+
useEffect(()=>{
|
|
128
|
+
const gl = glRef.current;
|
|
129
|
+
const canvas = canvasRef.current;
|
|
130
|
+
if (!gl || !canvas) return;
|
|
131
|
+
onError?.(null);
|
|
132
|
+
try {
|
|
133
|
+
// Create shaders
|
|
134
|
+
const vertexShader = createShader(gl, gl.VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
|
|
135
|
+
if (!vertexShader) throw new Error('devjar:gl Failed to create vertex shader');
|
|
136
|
+
const compiledFragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragment);
|
|
137
|
+
if (!compiledFragmentShader) throw new Error('devjar:gl Failed to create fragment shader');
|
|
138
|
+
// Create program
|
|
139
|
+
const program = createProgram(gl, vertexShader, compiledFragmentShader);
|
|
140
|
+
if (!program) throw new Error('devjar:gl Failed to create program');
|
|
141
|
+
// Clean up old program
|
|
142
|
+
if (programRef.current) {
|
|
143
|
+
gl.deleteProgram(programRef.current);
|
|
144
|
+
}
|
|
145
|
+
programRef.current = program;
|
|
146
|
+
// Clean up shaders (they're linked into the program)
|
|
147
|
+
gl.deleteShader(vertexShader);
|
|
148
|
+
gl.deleteShader(compiledFragmentShader);
|
|
149
|
+
// Render loop
|
|
150
|
+
const render = ()=>{
|
|
151
|
+
if (!gl || !canvas || !programRef.current || !positionBufferRef.current) return;
|
|
152
|
+
const program = programRef.current;
|
|
153
|
+
const positionBuffer = positionBufferRef.current;
|
|
154
|
+
// Clear
|
|
155
|
+
gl.clearColor(0, 0, 0, 1);
|
|
156
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
157
|
+
// Use program
|
|
158
|
+
gl.useProgram(program);
|
|
159
|
+
// Set up position attribute
|
|
160
|
+
const positionLocation = gl.getAttribLocation(program, 'a_position');
|
|
161
|
+
if (positionLocation >= 0) {
|
|
162
|
+
gl.enableVertexAttribArray(positionLocation);
|
|
163
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
164
|
+
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
165
|
+
}
|
|
166
|
+
// Set uniforms
|
|
167
|
+
const time = (performance.now() - startTimeRef.current) / 1000;
|
|
168
|
+
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
|
|
169
|
+
const timeLocation = gl.getUniformLocation(program, 'u_time');
|
|
170
|
+
const mouseLocation = gl.getUniformLocation(program, 'u_mouse');
|
|
171
|
+
if (resolutionLocation) {
|
|
172
|
+
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
|
|
173
|
+
}
|
|
174
|
+
if (timeLocation) {
|
|
175
|
+
gl.uniform1f(timeLocation, time);
|
|
176
|
+
}
|
|
177
|
+
if (mouseLocation) {
|
|
178
|
+
gl.uniform2f(mouseLocation, mouseRef.current.x, mouseRef.current.y);
|
|
179
|
+
}
|
|
180
|
+
// Draw
|
|
181
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
182
|
+
animationFrameRef.current = requestAnimationFrame(render);
|
|
183
|
+
};
|
|
184
|
+
// Start render loop
|
|
185
|
+
if (animationFrameRef.current) {
|
|
186
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
187
|
+
}
|
|
188
|
+
render();
|
|
189
|
+
// Cleanup function
|
|
190
|
+
return ()=>{
|
|
191
|
+
if (animationFrameRef.current) {
|
|
192
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
193
|
+
}
|
|
194
|
+
if (programRef.current && gl) {
|
|
195
|
+
gl.deleteProgram(programRef.current);
|
|
196
|
+
programRef.current = null;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
} catch (err) {
|
|
200
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
201
|
+
const prefixedMessage = errorMessage.startsWith('devjar:gl ') ? errorMessage : `devjar:gl ${errorMessage}`;
|
|
202
|
+
onError?.(prefixedMessage);
|
|
203
|
+
if (animationFrameRef.current) {
|
|
204
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}, [
|
|
208
|
+
fragment,
|
|
209
|
+
canvasRef,
|
|
210
|
+
onError
|
|
211
|
+
]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { useGL };
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { useRef, useState, useId, useEffect, useCallback } from 'react';
|
|
|
2
2
|
import { transform } from 'sucrase';
|
|
3
3
|
import { init, parse } from 'es-module-lexer';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
export { useGL } from './gl.js';
|
|
5
6
|
|
|
6
7
|
// declare esmsInitOptions on global window
|
|
7
8
|
async function createModule(files, { getModuleUrl }) {
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devjar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
7
|
+
"./gl": "./dist/gl.js",
|
|
7
8
|
"./package.json": "./package.json"
|
|
8
9
|
},
|
|
9
10
|
"license": "MIT",
|
|
@@ -11,17 +12,11 @@
|
|
|
11
12
|
"dist"
|
|
12
13
|
],
|
|
13
14
|
"types": "./dist/index.d.ts",
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "bunchee",
|
|
16
|
-
"prepublishOnly": "pnpm run build",
|
|
17
|
-
"build:site": "pnpm run build && next build ./site",
|
|
18
|
-
"start": "next start ./site",
|
|
19
|
-
"dev": "next dev ./site"
|
|
20
|
-
},
|
|
21
15
|
"peerDependencies": {
|
|
22
16
|
"react": "^18.2.0 || ^19.0.0"
|
|
23
17
|
},
|
|
24
18
|
"dependencies": {
|
|
19
|
+
"dedent": "^1.7.0",
|
|
25
20
|
"es-module-lexer": "1.6.0",
|
|
26
21
|
"es-module-shims": "2.0.3",
|
|
27
22
|
"sucrase": "3.35.0"
|
|
@@ -30,13 +25,18 @@
|
|
|
30
25
|
"@types/node": "^22.10.7",
|
|
31
26
|
"@types/react": "^19.0.7",
|
|
32
27
|
"@types/react-dom": "^19.0.3",
|
|
33
|
-
"bunchee": "^6.3.
|
|
34
|
-
"codice": "1.
|
|
35
|
-
"devjar": "link
|
|
36
|
-
"next": "^
|
|
37
|
-
"react": "^19.
|
|
38
|
-
"react-dom": "^19.
|
|
28
|
+
"bunchee": "^6.3.3",
|
|
29
|
+
"codice": "^1.5.4",
|
|
30
|
+
"devjar": "link:",
|
|
31
|
+
"next": "^16.0.3",
|
|
32
|
+
"react": "^19.2.0",
|
|
33
|
+
"react-dom": "^19.2.0",
|
|
39
34
|
"typescript": "^5.7.3"
|
|
40
35
|
},
|
|
41
|
-
"
|
|
42
|
-
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "bunchee",
|
|
38
|
+
"build:site": "pnpm run build && next build ./site",
|
|
39
|
+
"start": "next start ./site",
|
|
40
|
+
"dev": "next dev ./site"
|
|
41
|
+
}
|
|
42
|
+
}
|