pyzaist
pyzaist7mo ago

Deno, TypeScript, ESBuild, WebGL, VSCode

Hello! As the title suggests, I am using these technologies to create a web application: * Deno * TypeScript * ESBuild * WebGL (which uses .glsl shader files) * VSCode My goal is to have a .ts file be able to import a .glsl file as a string, and for there to be no errors or warnings about this in VSCode. This is a very common workflow for frontend development involving WebGL. My understanding is that I need to create a modules.d.ts file at the root of my project that looks like so:
declare module "*.glsl" {
const value: string;
export default value;
}
declare module "*.glsl" {
const value: string;
export default value;
}
and then have Deno use it by having deno.json have a line like so:
{
"compilerOptions": {
"types": ["modules.d.ts"]
}
}
{
"compilerOptions": {
"types": ["modules.d.ts"]
}
}
I believe that this is all that should be needed to have any .ts file be able to import any .glsl as a string like so:
import fragmentShaderSource from "./fragmentShader.glsl";
import vertexShaderSource from "./vertexShader.glsl";
import fragmentShaderSource from "./fragmentShader.glsl";
import vertexShaderSource from "./vertexShader.glsl";
It appears that this actually works with ESBuild and the glsl plugin. However, in VSCode I am getting errors on the above import lines like so: Module '"file:///<root path of my project>/fragmentShader.glsl"' has no default export So what I am doing wrong here, and how can I get VSCode to be happy?
20 Replies
pyzaist
pyzaist7mo ago
Types and Type Declarations | Deno Docs
One of the design principles of Deno is no non-standard module resolution. When
pyzaist
pyzaist7mo ago
hmmmm also when i do "deno check" i get this: error: Expected a JavaScript or TypeScript module, but identified a Unknown module. Importing these types of modules is currently not supported.
javi
javi7mo ago
You cannot do imports like that. The reason for which you do the declare module … is because under the hood, a bundler like webpack or rollup is used. Deno doesn't have those. One alternative is to just use Deno.readFile and the like
pyzaist
pyzaist7mo ago
Oof, that is sad
NDH
NDH7mo ago
Or just a .ts file with a single export
// ./f_shader.ts
export const f_glsl = `
precision lowp float;

attribute vec2 a_position; // Flat square on XY plane
attribute float a_startAngle;
attribute float a_angularVelocity;
attribute float a_rotationAxisAngle;
attribute float a_particleDistance;
attribute float a_particleAngle;
attribute float a_particleY;
uniform float u_time; // Global state
...
`
// ./f_shader.ts
export const f_glsl = `
precision lowp float;

attribute vec2 a_position; // Flat square on XY plane
attribute float a_startAngle;
attribute float a_angularVelocity;
attribute float a_rotationAxisAngle;
attribute float a_particleDistance;
attribute float a_particleAngle;
attribute float a_particleY;
uniform float u_time; // Global state
...
`
import { f_glsl } from './f_shader.ts' /* see above */
const gl = canvas.getContext("webgl");
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// shaderSource(shader: WebGLShader, source: string): void
gl.shaderSource(fragmentShader, f_glsl);
gl.compileShader(fragmentShader);
import { f_glsl } from './f_shader.ts' /* see above */
const gl = canvas.getContext("webgl");
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
// shaderSource(shader: WebGLShader, source: string): void
gl.shaderSource(fragmentShader, f_glsl);
gl.compileShader(fragmentShader);
https://www.typescriptlang.org/play#example/typescript-with-webgl deno_esbuild will love this simple string import. https://github.com/esbuild/deno-esbuild
A deno_esbuild example from Hot-server: https://github.com/nhrones/Hot/blob/main/builder.ts usage:
import { build } from './builder.ts'
try {
build({entry: "./src/main.ts", minify: true, out: "./dist/bundle.js" })
.then(() => console.log('Built bundle.js!');
}).catch((err) => {
console.info('Build err - ', err)
})
import { build } from './builder.ts'
try {
build({entry: "./src/main.ts", minify: true, out: "./dist/bundle.js" })
.then(() => console.log('Built bundle.js!');
}).catch((err) => {
console.info('Build err - ', err)
})
pyzaist
pyzaist7mo ago
Yeah I currently have a .ts file with a single import of a string, but there’s a few sad things about this: 1) it treats glsl code as second class rather than first class, and more tangibly 2) it doesn’t allow syntax highlighting in vs code Also Deno.readFile won’t work because this is for the front end. So I want the file contents in the bundle
NDH
NDH7mo ago
Have you considered WebGPU and the WebGPU Shading Language? WGSL It's now support in Deno https://deno.com/blog/v1.39
pyzaist
pyzaist7mo ago
Hey nice! So I don’t have much experience with WebGPU but my understanding is that it gives more control at the cost of more complexity. So would def prefer WegGL
NDH
NDH7mo ago
Good luck. I still think you can edit GLSL with a good extension, and then just save it as text in your build step!
pyzaist
pyzaist7mo ago
Got it. I will play around with some things then and probably post back here with what I come up with, for posterity hmm okay so im checking this out today. it appears that 1) webGPU is not widely supported yet (https://caniuse.com/webgpu) but likely will be soon and 2) even if it was, i would have the same problem with WGSL as i do with GLSL - so it doesnt really benefit me to use it but from what i am reading, this comment does not seem to be true. the import setup i did was for the typescript compiler. it is a part of typescript itself.
pyzaist
pyzaist7mo ago
issue from a long time ago that discusses this: https://github.com/Microsoft/TypeScript/issues/2709
GitHub
Importing files other than TS modules · Issue #2709 · microsoft/Typ...
I've successfully been using SystemJS and TypeScript for a while now. I've been about to use the CommonJS syntax to easily import other TS modules, as well as other resources: var OtherModu...
AapoAlas
AapoAlas7mo ago
I believe somewhat soonish you'll be able to do:
import data from "./fragmentShared.glsl" with { type: "string" };
import data from "./fragmentShared.glsl" with { type: "string" };
That will then just read the file as plaintext and include it into the module graph.
bartlomieju
bartlomieju7mo ago
Correct, we're working on these kind of imports
pyzaist
pyzaist7mo ago
Oh that’s awesome! That would work great Is there somewhere I could follow along on that progress?
bartlomieju
bartlomieju7mo ago
GitHub
feat: Allow embedders to load custom module types by bartlomieju · ...
More generic solution than #344 and #357. It allows embedders to load any module type they want. Still some rough edges that need to be fixed. I will probably split this PR into a few more.
pyzaist
pyzaist7mo ago
Awesome, thanks! hmmmmm okay i was going to ask why the plan is not to replicate how typescript does this as i discussed above, but i think im starting to get it. in the non-deno case, there is a loader/bundler that takes all my TS and plaintext imports and handles the actual turning it into runnable JS code. however in the deno case, TS is the native language, so it needs to be able to run the code without all this. so it cant simply say "this file will be a string" to satisfy the type system, it needs to know what particular string it will be. is that getting at the right explanation? until the new import method is available as you mentioned above, im leaning towards having a pre-build step that takes these two files im working in and generating a ts file that outputs the contents as a string. i think thatll do fine for a quick solution. and this is a small hobby project, so it doesnt need to be completely ideal
AapoAlas
AapoAlas7mo ago
So basically declare module *.foo explains to the TypeScript compiler that you expect Someone (TSC doesn't care who) to convert imports pointing to imports of this style into whatever you declare in the declare declaration. Because Deno handles transforming the code, Deno needs to be the "Someone" from above doing the transformation. But now Deno is both the loader doing the transformation AND the compiler doing the type checking. It doesn't make sense to do the declare *.foo to tell Deno what the types of "*.foo" imports are PLUSA some other declaration to tell Deno that a particular import should be handled as a plaintext import. It's simpler (at least from an implementation standpoint) to only provide the way to implement as plaintext and derive the type directly based on that. (Note that this is more general than the converse as now you can import eg. JS files as plaintext as well.)
pyzaist
pyzaist7mo ago
yeah yeah, exactly. okay that makes a ton of sense. from a user perspective, i will still say that it is unfortunate that i would have to write with { type: "string" } after every GLSL (or CSS, or HTML, or whatever plaintext file i would use) import i do, instead of specify in one place that GLSL files should always be imported in this way. for my use case this is totally fine, but just providing signal to you all for how i would anticipate larger projects would prefer to engage with this topic
bartlomieju
bartlomieju7mo ago
The approach were taking plays nicely with other in-progress tc39 proposals and is completely in-bound solution in contrast to having some auxiliary files that need to tell how certain file have to be interpreted. Sure it's a few more keystrokes but give a lot more control and allow ecosystem to unify behind a single solution (import attributes) That said, you can still use a pre build step if it fits your use case better!
pyzaist
pyzaist7mo ago
ah got it, that adds more clarity for me! import attributes are new to me so i will look into them more. very much appreciate the responses and thoughtfulness you all take. thanks again!