@@ -15,11 +15,6 @@ let slangPromise = null
1515 * @returns {import('vite').PluginOption }
1616 */
1717function viteSlang ( options = { target : 'WGSL' } ) {
18- /**
19- * @type {import('./slang-2025.15-wasm/slang-wasm.js').Session | null }
20- */
21- let session = null
22-
2318 return {
2419 name : 'vite-slang' ,
2520 async transform ( code , id ) {
@@ -28,11 +23,14 @@ function viteSlang(options = { target: 'WGSL' }) {
2823 // For now, we only handle files with a .slang file extension. These are transformed as source code.
2924 if ( ! id . endsWith ( '.slang' ) ) return
3025
31- // Lazy load Slang WASM so this module can be used in ESM/CJS/UMD contexts (no top-level-await)
32- if ( ! slangPromise ) slangPromise = slangModule ( )
33- const slang = await slangPromise
26+ /** @type {import('./slang-2025.15-wasm/slang-wasm.js').Session | null } */
27+ let session = null
28+
29+ try {
30+ // Lazy load Slang WASM so this module can be used in ESM/CJS/UMD contexts (no top-level-await)
31+ if ( ! slangPromise ) slangPromise = slangModule ( )
32+ const slang = await slangPromise
3433
35- if ( ! session ) {
3634 let wasmCompileTarget = - 1
3735 for ( const target of slang . getCompileTargets ( ) ) {
3836 if ( target . name == options . target ) {
@@ -43,44 +41,54 @@ function viteSlang(options = { target: 'WGSL' }) {
4341 // TODO: under which scenarios is a session null? Examples expect GlobalSession but not a Session?
4442 const globalSession = slang . createGlobalSession ( )
4543 session = globalSession . createSession ( wasmCompileTarget )
46- }
4744
48- // TODO: module stitching and/or non-standard preprocessor unfolding?
49- /** @type {import('./slang-2025.15-wasm/slang-wasm.js').Module } */
50- const module = session . loadModuleFromSource ( code , 'shader' , id )
51- /** @type {import('./slang-2025.15-wasm/slang-wasm.js').Module[] } */
52- const components = [ module ]
45+ // TODO: module stitching and/or non-standard preprocessor unfolding?
46+ /** @type {import('./slang-2025.15-wasm/slang-wasm.js').Module } */
47+ const module = session . loadModuleFromSource ( code , 'shader' , id )
48+ /** @type {import('./slang-2025.15-wasm/slang-wasm.js').Module[] } */
5349
54- // TODO: surely, there's a better way to reflect the program and get a top-level layout?
55- const count = module . getDefinedEntryPointCount ( )
56- for ( let i = 0 ; i < count ; i ++ ) {
57- /** @type {import('./slang-2025.15-wasm/slang-wasm.js').EntryPoint } */
58- const entryPoint = module . getDefinedEntryPoint ( i )
59- const program = session . createCompositeComponentType ( [ entryPoint , 1 ] )
60- const layout = program . getLayout ( 0 ) . toJsonObject ( )
61- const { name, stage } = layout . entryPoints [ 0 ]
62- components . push ( module . findAndCheckEntryPoint ( name , SLANG_STAGES [ stage ] ) )
63- }
50+ // Surface compilation errors
51+ if ( ! module ) {
52+ const error = slang . getLastError ( )
53+ throw new Error ( `${ error . type } error: ${ error . message } ` )
54+ }
55+
56+ // TODO: is there a way to instead infer based on SV_VertexID or SV_Target?
57+ const count = module . getDefinedEntryPointCount ( )
58+ if ( count === 0 ) {
59+ throw new Error (
60+ 'An entrypoint must be defined with a shader stage attribute! Try adding [shader("fragment")] before your entrypoint method.' ,
61+ )
62+ }
6463
65- /** @type {import('./slang-2025.15-wasm/slang-wasm.js').ComponentType } */
66- const linkedProgram = session . createCompositeComponentType ( components ) . link ( )
64+ // TODO: surely, there's a better way to reflect the program and get a top-level layout?
65+ const components = [ module ]
66+ for ( let i = 0 ; i < count ; i ++ ) {
67+ /** @type {import('./slang-2025.15-wasm/slang-wasm.js').EntryPoint } */
68+ const entryPoint = module . getDefinedEntryPoint ( i )
69+ const program = session . createCompositeComponentType ( [ entryPoint , 1 ] )
70+ const layout = program . getLayout ( 0 ) . toJsonObject ( )
71+ const { name, stage } = layout . entryPoints [ 0 ]
72+ components . push ( module . findAndCheckEntryPoint ( name , SLANG_STAGES [ stage ] ) )
73+ }
6774
68- code = ''
69- for ( let i = 0 ; i < count ; i ++ ) {
70- code += linkedProgram . getEntryPointCode ( i /* entryPointIndex */ , 0 /* targetIndex */ ) + '\n'
71- }
72- code . trim ( )
75+ /** @type {import('./slang-2025.15-wasm/slang-wasm.js').ComponentType } */
76+ const linkedProgram = session . createCompositeComponentType ( components ) . link ( )
7377
74- if ( code == '' ) {
75- const error = slang . getLastError ( )
76- throw new Error ( `${ error . type } error: ${ error . message } ` )
77- }
78+ code = ''
79+ for ( let i = 0 ; i < count ; i ++ ) {
80+ code += linkedProgram . getEntryPointCode ( i /* entryPointIndex */ , 0 /* targetIndex */ ) + '\n'
81+ }
82+ code . trim ( )
7883
79- return transformWithEsbuild ( code , id , {
80- format : 'esm' ,
81- loader : 'text' ,
82- sourcemap : 'external' , // TODO: pass to WebGPU API?
83- } )
84+ return transformWithEsbuild ( code , id , {
85+ format : 'esm' ,
86+ loader : 'text' ,
87+ sourcemap : 'external' , // TODO: pass to WebGPU API?
88+ } )
89+ } finally {
90+ if ( session ) session . delete ( )
91+ }
8492 } ,
8593 }
8694}
0 commit comments