Skip to content

Commit b6fccc4

Browse files
fix: forward compilation errors, fix HMR
1 parent f664ffc commit b6fccc4

File tree

1 file changed

+49
-41
lines changed

1 file changed

+49
-41
lines changed

src/index.js

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ let slangPromise = null
1515
* @returns {import('vite').PluginOption}
1616
*/
1717
function 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

Comments
 (0)