wgsl-renderer 0.0.1
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 +9 -0
- package/README.md +308 -0
- package/dist/cjs/index.js +438 -0
- package/dist/esm/index.js +437 -0
- package/dist/types/index.d.ts +140 -0
- package/dist/types/index.js +437 -0
- package/package.json +52 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
//#region src/RenderPass.ts
|
|
2
|
+
var RenderPass = class {
|
|
3
|
+
name;
|
|
4
|
+
pipeline;
|
|
5
|
+
bindGroup;
|
|
6
|
+
vertexBuffer;
|
|
7
|
+
clearColor;
|
|
8
|
+
blendMode;
|
|
9
|
+
hasOutputTexture = false;
|
|
10
|
+
constructor(descriptor, device, format, layout) {
|
|
11
|
+
this.name = descriptor.name;
|
|
12
|
+
this.clearColor = descriptor.clearColor || {
|
|
13
|
+
r: 0,
|
|
14
|
+
g: 0,
|
|
15
|
+
b: 0,
|
|
16
|
+
a: 1
|
|
17
|
+
};
|
|
18
|
+
this.blendMode = descriptor.blendMode || "alpha";
|
|
19
|
+
const module = device.createShaderModule({ code: descriptor.shaderCode });
|
|
20
|
+
this.vertexBuffer = device.createBuffer({
|
|
21
|
+
size: 36,
|
|
22
|
+
usage: GPUBufferUsage.VERTEX,
|
|
23
|
+
mappedAtCreation: true
|
|
24
|
+
});
|
|
25
|
+
new Float32Array(this.vertexBuffer.getMappedRange()).set([
|
|
26
|
+
-1,
|
|
27
|
+
-1,
|
|
28
|
+
0,
|
|
29
|
+
3,
|
|
30
|
+
-1,
|
|
31
|
+
0,
|
|
32
|
+
-1,
|
|
33
|
+
3,
|
|
34
|
+
0
|
|
35
|
+
]);
|
|
36
|
+
this.vertexBuffer.unmap();
|
|
37
|
+
this.pipeline = device.createRenderPipeline({
|
|
38
|
+
layout,
|
|
39
|
+
vertex: {
|
|
40
|
+
module,
|
|
41
|
+
entryPoint: "vs_main",
|
|
42
|
+
buffers: [{
|
|
43
|
+
arrayStride: 12,
|
|
44
|
+
attributes: [{
|
|
45
|
+
shaderLocation: 0,
|
|
46
|
+
offset: 0,
|
|
47
|
+
format: "float32x3"
|
|
48
|
+
}]
|
|
49
|
+
}]
|
|
50
|
+
},
|
|
51
|
+
fragment: {
|
|
52
|
+
module,
|
|
53
|
+
entryPoint: "fs_main",
|
|
54
|
+
targets: [{
|
|
55
|
+
format,
|
|
56
|
+
blend: this.getBlendState()
|
|
57
|
+
}]
|
|
58
|
+
},
|
|
59
|
+
primitive: { topology: "triangle-list" }
|
|
60
|
+
});
|
|
61
|
+
const bindGroupLayout = this.pipeline.getBindGroupLayout(0);
|
|
62
|
+
this.bindGroup = device.createBindGroup({
|
|
63
|
+
layout: bindGroupLayout,
|
|
64
|
+
entries: descriptor.bindGroupEntries || []
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
getBlendState() {
|
|
68
|
+
switch (this.blendMode) {
|
|
69
|
+
case "none": return;
|
|
70
|
+
case "alpha": return {
|
|
71
|
+
color: {
|
|
72
|
+
srcFactor: "src-alpha",
|
|
73
|
+
dstFactor: "one-minus-src-alpha",
|
|
74
|
+
operation: "add"
|
|
75
|
+
},
|
|
76
|
+
alpha: {
|
|
77
|
+
srcFactor: "one",
|
|
78
|
+
dstFactor: "one-minus-src-alpha",
|
|
79
|
+
operation: "add"
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
case "additive": return {
|
|
83
|
+
color: {
|
|
84
|
+
srcFactor: "src-alpha",
|
|
85
|
+
dstFactor: "one",
|
|
86
|
+
operation: "add"
|
|
87
|
+
},
|
|
88
|
+
alpha: {
|
|
89
|
+
srcFactor: "one",
|
|
90
|
+
dstFactor: "one",
|
|
91
|
+
operation: "add"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
case "multiply": return {
|
|
95
|
+
color: {
|
|
96
|
+
srcFactor: "src",
|
|
97
|
+
dstFactor: "dst",
|
|
98
|
+
operation: "add"
|
|
99
|
+
},
|
|
100
|
+
alpha: {
|
|
101
|
+
srcFactor: "one",
|
|
102
|
+
dstFactor: "one-minus-src-alpha",
|
|
103
|
+
operation: "add"
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
default: return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/TextureManager.ts
|
|
113
|
+
var TextureManager = class {
|
|
114
|
+
textures = /* @__PURE__ */ new Map();
|
|
115
|
+
device;
|
|
116
|
+
width;
|
|
117
|
+
height;
|
|
118
|
+
constructor(device, width, height) {
|
|
119
|
+
this.device = device;
|
|
120
|
+
this.width = width;
|
|
121
|
+
this.height = height;
|
|
122
|
+
}
|
|
123
|
+
createTexture(name, format) {
|
|
124
|
+
if (this.textures.has(name)) this.textures.get(name).destroy();
|
|
125
|
+
const texture = this.device.createTexture({
|
|
126
|
+
size: [this.width, this.height],
|
|
127
|
+
format: format || "bgra8unorm",
|
|
128
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
|
|
129
|
+
});
|
|
130
|
+
this.textures.set(name, texture);
|
|
131
|
+
return texture;
|
|
132
|
+
}
|
|
133
|
+
getTexture(name) {
|
|
134
|
+
return this.textures.get(name);
|
|
135
|
+
}
|
|
136
|
+
resize(width, height) {
|
|
137
|
+
if (width === this.width && height === this.height) return;
|
|
138
|
+
this.textures.forEach((texture) => texture.destroy());
|
|
139
|
+
this.textures.clear();
|
|
140
|
+
this.width = width;
|
|
141
|
+
this.height = height;
|
|
142
|
+
}
|
|
143
|
+
destroy() {
|
|
144
|
+
this.textures.forEach((texture) => texture.destroy());
|
|
145
|
+
this.textures.clear();
|
|
146
|
+
}
|
|
147
|
+
getPixelSize() {
|
|
148
|
+
return {
|
|
149
|
+
width: this.width,
|
|
150
|
+
height: this.height
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/index.ts
|
|
157
|
+
var WGSLRenderer = class {
|
|
158
|
+
ctx;
|
|
159
|
+
device;
|
|
160
|
+
format;
|
|
161
|
+
passes = [];
|
|
162
|
+
textureManager;
|
|
163
|
+
backgroundPassAdded = false;
|
|
164
|
+
backgroundColor = {
|
|
165
|
+
r: .1,
|
|
166
|
+
g: .1,
|
|
167
|
+
b: .1,
|
|
168
|
+
a: 1
|
|
169
|
+
};
|
|
170
|
+
uniforms = /* @__PURE__ */ new Map();
|
|
171
|
+
animationFrameId = null;
|
|
172
|
+
constructor(canvas, options) {
|
|
173
|
+
this.canvas = canvas;
|
|
174
|
+
if (!navigator.gpu) throw new Error("WebGPU is not supported in this browser.");
|
|
175
|
+
this.ctx = canvas.getContext("webgpu");
|
|
176
|
+
switch (typeof options?.backgroundColor) {
|
|
177
|
+
case "number":
|
|
178
|
+
const hex = options.backgroundColor;
|
|
179
|
+
this.backgroundColor = {
|
|
180
|
+
r: (hex >> 16 & 255) / 255,
|
|
181
|
+
g: (hex >> 8 & 255) / 255,
|
|
182
|
+
b: (hex & 255) / 255,
|
|
183
|
+
a: 1
|
|
184
|
+
};
|
|
185
|
+
break;
|
|
186
|
+
case "string":
|
|
187
|
+
const m = options.backgroundColor.match(/^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i);
|
|
188
|
+
if (m) this.backgroundColor = {
|
|
189
|
+
r: Number.parseInt(m[1], 16) / 255,
|
|
190
|
+
g: Number.parseInt(m[2], 16) / 255,
|
|
191
|
+
b: Number.parseInt(m[3], 16) / 255,
|
|
192
|
+
a: 1
|
|
193
|
+
};
|
|
194
|
+
break;
|
|
195
|
+
case "object":
|
|
196
|
+
Object.assign(this.backgroundColor, options.backgroundColor);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async init() {
|
|
201
|
+
this.device = await (await navigator.gpu.requestAdapter()).requestDevice();
|
|
202
|
+
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
203
|
+
this.ctx.configure({
|
|
204
|
+
device: this.device,
|
|
205
|
+
format: this.format,
|
|
206
|
+
alphaMode: "opaque"
|
|
207
|
+
});
|
|
208
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
209
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
210
|
+
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
211
|
+
this.ensureBackgroundPass();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Ensure background pass is added
|
|
215
|
+
*/
|
|
216
|
+
ensureBackgroundPass() {
|
|
217
|
+
if (!this.backgroundPassAdded) {
|
|
218
|
+
const backgroundPass = new RenderPass({
|
|
219
|
+
name: "builtin_background",
|
|
220
|
+
shaderCode: `
|
|
221
|
+
@vertex
|
|
222
|
+
fn vs_main(@location(0) p: vec3<f32>) -> @builtin(position) vec4<f32> {
|
|
223
|
+
return vec4<f32>(p, 1.0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@fragment
|
|
227
|
+
fn fs_main() -> @location(0) vec4<f32> {
|
|
228
|
+
return vec4<f32>(${this.backgroundColor.r}, ${this.backgroundColor.g}, ${this.backgroundColor.b}, ${this.backgroundColor.a});
|
|
229
|
+
}
|
|
230
|
+
`,
|
|
231
|
+
blendMode: "none",
|
|
232
|
+
clearColor: this.backgroundColor,
|
|
233
|
+
bindGroupEntries: []
|
|
234
|
+
}, this.device, this.format, "auto");
|
|
235
|
+
this.passes.unshift(backgroundPass);
|
|
236
|
+
this.backgroundPassAdded = true;
|
|
237
|
+
this.textureManager.createTexture("pass_0_output", this.format);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Add a render pass to the multi-pass pipeline
|
|
242
|
+
*/
|
|
243
|
+
addPass(descriptor) {
|
|
244
|
+
const finalBindGroupEntries = [];
|
|
245
|
+
if (this.passes.length > 0) {
|
|
246
|
+
const previousOutput = this.getPassOutput(this.passes.length - 1);
|
|
247
|
+
if (previousOutput) finalBindGroupEntries.push({
|
|
248
|
+
binding: 0,
|
|
249
|
+
resource: previousOutput.createView()
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
descriptor.resources.forEach((resource, index) => {
|
|
253
|
+
finalBindGroupEntries.push({
|
|
254
|
+
binding: index + 1,
|
|
255
|
+
resource
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
const pass = new RenderPass({
|
|
259
|
+
name: descriptor.name,
|
|
260
|
+
shaderCode: descriptor.shaderCode,
|
|
261
|
+
clearColor: descriptor.clearColor,
|
|
262
|
+
blendMode: descriptor.blendMode,
|
|
263
|
+
bindGroupEntries: finalBindGroupEntries
|
|
264
|
+
}, this.device, this.format, "auto");
|
|
265
|
+
this.passes.push(pass);
|
|
266
|
+
const currentPassIndex = this.passes.length - 1;
|
|
267
|
+
const textureName = `pass_${currentPassIndex}_output`;
|
|
268
|
+
this.textureManager.createTexture(textureName, this.format);
|
|
269
|
+
this.passes[currentPassIndex].hasOutputTexture = true;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Set background color
|
|
273
|
+
*/
|
|
274
|
+
setBackgroundColor(r, g, b, a = 1) {
|
|
275
|
+
this.backgroundColor = {
|
|
276
|
+
r,
|
|
277
|
+
g,
|
|
278
|
+
b,
|
|
279
|
+
a
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Force create output texture for a specific pass
|
|
284
|
+
* This is useful when you need the output texture immediately after adding a pass
|
|
285
|
+
*/
|
|
286
|
+
createPassOutput(passIndex) {
|
|
287
|
+
if (passIndex < 0 || passIndex >= this.passes.length) return;
|
|
288
|
+
if (passIndex === this.passes.length - 1) return;
|
|
289
|
+
const textureName = `pass_${passIndex}_output`;
|
|
290
|
+
return this.textureManager.createTexture(textureName, this.format);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get the output texture of a specific pass
|
|
294
|
+
*/
|
|
295
|
+
getPassOutput(passIndex) {
|
|
296
|
+
if (passIndex < 0 || passIndex >= this.passes.length) return;
|
|
297
|
+
if (passIndex !== 0 && passIndex === this.passes.length - 1) {
|
|
298
|
+
if (!this.passes[passIndex]?.hasOutputTexture) return;
|
|
299
|
+
}
|
|
300
|
+
const textureName = `pass_${passIndex}_output`;
|
|
301
|
+
return this.textureManager.getTexture(textureName);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Create a uniforms
|
|
305
|
+
* @param length The length of the uniform buffer in number of floats
|
|
306
|
+
* @return The uniform object containing the buffer and data array
|
|
307
|
+
*/
|
|
308
|
+
createUniforms(length) {
|
|
309
|
+
const values = new Float32Array(Math.ceil(length));
|
|
310
|
+
const buffer = this.device.createBuffer({
|
|
311
|
+
size: values.byteLength,
|
|
312
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
313
|
+
});
|
|
314
|
+
const uniformID = Symbol();
|
|
315
|
+
const uniforms = {
|
|
316
|
+
id: uniformID,
|
|
317
|
+
values,
|
|
318
|
+
apply: () => {
|
|
319
|
+
this.device.queue.writeBuffer(buffer, 0, values.buffer, values.byteOffset, values.byteLength);
|
|
320
|
+
},
|
|
321
|
+
getBuffer: () => buffer
|
|
322
|
+
};
|
|
323
|
+
this.uniforms.set(uniformID, uniforms);
|
|
324
|
+
return uniforms;
|
|
325
|
+
}
|
|
326
|
+
getUniformsByID(id) {
|
|
327
|
+
return this.uniforms.get(id);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Create a sampler
|
|
331
|
+
*/
|
|
332
|
+
createSampler(options) {
|
|
333
|
+
return this.device.createSampler(Object.assign({
|
|
334
|
+
magFilter: "linear",
|
|
335
|
+
minFilter: "linear",
|
|
336
|
+
addressModeU: "clamp-to-edge",
|
|
337
|
+
addressModeV: "clamp-to-edge"
|
|
338
|
+
}, options));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Create a bind group entry for texture
|
|
342
|
+
*/
|
|
343
|
+
createTextureBinding(texture) {
|
|
344
|
+
return texture.createView();
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Configure multi-pass rendering
|
|
348
|
+
*/
|
|
349
|
+
setupMultiPass(descriptor) {
|
|
350
|
+
this.passes = [];
|
|
351
|
+
this.textureManager.destroy();
|
|
352
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
353
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
354
|
+
this.textureManager = new TextureManager(this.device, canvasWidth, canvasHeight);
|
|
355
|
+
descriptor.passes.forEach((passDesc) => this.addPass(passDesc));
|
|
356
|
+
if (descriptor.output?.texture && !descriptor.output.writeToCanvas) this.textureManager.createTexture("final_output");
|
|
357
|
+
}
|
|
358
|
+
async loadTexture(url) {
|
|
359
|
+
const resp = fetch(url);
|
|
360
|
+
resp.catch((err) => {
|
|
361
|
+
console.error("Failed to load texture:", err);
|
|
362
|
+
});
|
|
363
|
+
const res = await resp;
|
|
364
|
+
const imgBitmap = await createImageBitmap(await res.blob());
|
|
365
|
+
const texture = this.device.createTexture({
|
|
366
|
+
size: [
|
|
367
|
+
imgBitmap.width,
|
|
368
|
+
imgBitmap.height,
|
|
369
|
+
1
|
|
370
|
+
],
|
|
371
|
+
format: "rgba8unorm",
|
|
372
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
|
|
373
|
+
});
|
|
374
|
+
this.device.queue.copyExternalImageToTexture({ source: imgBitmap }, { texture }, [imgBitmap.width, imgBitmap.height]);
|
|
375
|
+
return {
|
|
376
|
+
texture,
|
|
377
|
+
width: imgBitmap.width,
|
|
378
|
+
height: imgBitmap.height
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
renderFrame() {
|
|
382
|
+
if (this.passes.length === 0) return;
|
|
383
|
+
const commandEncoder = this.device.createCommandEncoder();
|
|
384
|
+
const canvasWidth = this.canvas.width || this.canvas.clientWidth;
|
|
385
|
+
const canvasHeight = this.canvas.height || this.canvas.clientHeight;
|
|
386
|
+
this.textureManager.resize(canvasWidth, canvasHeight);
|
|
387
|
+
for (let i = 0; i < this.passes.length; i++) {
|
|
388
|
+
const pass = this.passes[i];
|
|
389
|
+
let renderTarget;
|
|
390
|
+
let loadOp = "clear";
|
|
391
|
+
if (i === this.passes.length - 1) renderTarget = this.ctx.getCurrentTexture().createView();
|
|
392
|
+
else {
|
|
393
|
+
const textureName = `pass_${i}_output`;
|
|
394
|
+
const texture = this.textureManager.getTexture(textureName);
|
|
395
|
+
if (!texture) continue;
|
|
396
|
+
renderTarget = texture.createView();
|
|
397
|
+
loadOp = "load";
|
|
398
|
+
}
|
|
399
|
+
const renderPass = commandEncoder.beginRenderPass({ colorAttachments: [{
|
|
400
|
+
view: renderTarget,
|
|
401
|
+
loadOp: i === 0 ? "clear" : loadOp,
|
|
402
|
+
storeOp: "store",
|
|
403
|
+
clearValue: pass.clearColor
|
|
404
|
+
}] });
|
|
405
|
+
renderPass.setPipeline(pass.pipeline);
|
|
406
|
+
if (pass.bindGroup) renderPass.setBindGroup(0, pass.bindGroup);
|
|
407
|
+
renderPass.setVertexBuffer(0, pass.vertexBuffer);
|
|
408
|
+
renderPass.draw(3, 1, 0, 0);
|
|
409
|
+
renderPass.end();
|
|
410
|
+
}
|
|
411
|
+
this.device.queue.submit([commandEncoder.finish()]);
|
|
412
|
+
}
|
|
413
|
+
loopRender(cb) {
|
|
414
|
+
cb?.();
|
|
415
|
+
this.renderFrame();
|
|
416
|
+
this.animationFrameId = requestAnimationFrame(() => this.loopRender(cb));
|
|
417
|
+
}
|
|
418
|
+
stopLoop() {
|
|
419
|
+
if (this.animationFrameId !== null) {
|
|
420
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
421
|
+
this.animationFrameId = null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
resize(width, height) {
|
|
425
|
+
this.canvas.width = width;
|
|
426
|
+
this.canvas.height = height;
|
|
427
|
+
this.textureManager.resize(width, height);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
async function createWGSLRenderer(cvs, options) {
|
|
431
|
+
const renderer = new WGSLRenderer(cvs, options);
|
|
432
|
+
await renderer.init();
|
|
433
|
+
return renderer;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
//#endregion
|
|
437
|
+
export { createWGSLRenderer };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wgsl-renderer",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "一个基于WebGPU和WGSL的多通道渲染器",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/taiyuuki/wgsl-renderer"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/taiyuuki/wgsl-renderer/issues",
|
|
14
|
+
"homepage": "https://github.com/taiyuuki/wgsl-renderer#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"webgpu",
|
|
17
|
+
"wgsl",
|
|
18
|
+
"renderer",
|
|
19
|
+
"graphics",
|
|
20
|
+
"webgl",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/esm/index.js",
|
|
28
|
+
"require": "./dist/cjs/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"author": "taiyuuki <taiyuuki@qq.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@taiyuuki/eslint-config": "^1.4.14",
|
|
38
|
+
"@types/node": "^20.14.2",
|
|
39
|
+
"@webgpu/types": "^0.1.66",
|
|
40
|
+
"eslint": "^9.5.0",
|
|
41
|
+
"rimraf": "^6.1.2",
|
|
42
|
+
"rolldown": "1.0.0-beta.52",
|
|
43
|
+
"rolldown-plugin-dts": "^0.18.1",
|
|
44
|
+
"typescript": "^5.4.5"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"lint": "eslint --fix",
|
|
48
|
+
"build": "rolldown -c",
|
|
49
|
+
"clean": "rimraf dist",
|
|
50
|
+
"prebuild": "npm run clean"
|
|
51
|
+
}
|
|
52
|
+
}
|