UNPKG

bun-plugin-glsl

Version:

Import, inline (and minify) GLSL/WGSL shader files

179 lines (177 loc) 6.06 kB
// @bun // plugin/src/loadShader.js import { dirname, resolve, extname, posix, sep } from "path"; import { emitWarning, cwd } from "process"; import { readFileSync } from "fs"; var recursiveChunk = ""; var allChunks = new Set; var dependentChunks = new Map; var duplicatedChunks = new Map; var include = /#include(\s+([^\s<>]+));?/gi; function resetSavedChunks() { const chunk = recursiveChunk; duplicatedChunks.clear(); dependentChunks.clear(); recursiveChunk = ""; allChunks.clear(); return chunk; } function getRecursionCaller() { const dependencies = [...dependentChunks.keys()]; return dependencies[dependencies.length - 1]; } function checkDuplicatedImports(path) { const caller = getRecursionCaller(); const chunks = duplicatedChunks.get(caller) ?? []; if (chunks.includes(path)) return; chunks.push(path); duplicatedChunks.set(caller, chunks); emitWarning(`'${path}' was included multiple times.`, { code: "vite-plugin-glsl", detail: "Please avoid multiple imports of the same chunk in order to avoid" + ` recursions and optimize your shader length. Duplicated import found in file '${caller}'.` }); } function removeSourceComments(source, triple = false) { if (source.includes("/*") && source.includes("*/")) { source = source.slice(0, source.indexOf("/*")) + source.slice(source.indexOf("*/") + 2, source.length); } const lines = source.split(` `); for (let l = lines.length;l--; ) { const index = lines[l].indexOf("//"); if (index > -1) { if (lines[l][index + 2] === "/" && !include.test(lines[l]) && !triple) continue; lines[l] = lines[l].slice(0, lines[l].indexOf("//")); } } return lines.join(` `); } function checkRecursiveImports(path, warn, ignore) { if (allChunks.has(path)) { if (ignore) return null; warn && checkDuplicatedImports(path); } return checkIncludedDependencies(path, path); } function checkIncludedDependencies(path, root) { const dependencies = dependentChunks.get(path); let recursiveDependency = false; if (dependencies?.includes(root)) { recursiveChunk = root; return true; } dependencies?.forEach((dependency) => recursiveDependency ||= checkIncludedDependencies(dependency, root)); return recursiveDependency; } function minifyShader(shader, newLine = false) { return shader.replace(/\\(?:\r\n|\n\r|\n|\r)|\/\*.*?\*\/|\/\/(?:\\(?:\r\n|\n\r|\n|\r)|[^\n\r])*/g, "").split(/\n+/).reduce((result, line) => { line = line.trim().replace(/\s{2,}|\t/, " "); if (/@(vertex|fragment|compute)/.test(line) || line.endsWith("return")) line += " "; if (line[0] === "#") { newLine && result.push(` `); result.push(line, ` `); newLine = false; } else { !line.startsWith("{") && result.length && result[result.length - 1].endsWith("else") && result.push(" "); result.push(line.replace(/\s*({|}|=|\*|,|\+|\/|>|<|&|\||\[|\]|\(|\)|\-|!|;)\s*/g, "$1")); newLine = true; } return result; }, []).join("").replace(/\n+/g, ` `); } function loadChunks(source, path, options) { const { warnDuplicatedImports, removeDuplicatedImports } = options; const unixPath = path.split(sep).join(posix.sep); const recursion = checkRecursiveImports(unixPath, warnDuplicatedImports, removeDuplicatedImports); if (recursion) return recursiveChunk; else if (recursion === null) return ""; source = removeSourceComments(source); let directory = dirname(unixPath); allChunks.add(unixPath); if (include.test(source)) { dependentChunks.set(unixPath, []); const currentDirectory = directory; const ext = options.defaultExtension; source = source.replace(include, (_, chunkPath) => { chunkPath = chunkPath.trim().replace(/^(?:"|')?|(?:"|')?;?$/gi, ""); if (!chunkPath.indexOf("/")) { const base = cwd().split(sep).join(posix.sep); chunkPath = base + options.root + chunkPath; } const directoryIndex = chunkPath.lastIndexOf("/"); directory = currentDirectory; if (directoryIndex !== -1) { directory = resolve(directory, chunkPath.slice(0, directoryIndex + 1)); chunkPath = chunkPath.slice(directoryIndex + 1, chunkPath.length); } let shader = resolve(directory, chunkPath); if (!extname(shader)) shader = `${shader}.${ext}`; const shaderPath = shader.split(sep).join(posix.sep); dependentChunks.get(unixPath)?.push(shaderPath); return loadChunks(readFileSync(shader, "utf8"), shader, options); }); } if (recursiveChunk) { const caller = getRecursionCaller(); const recursiveChunk2 = resetSavedChunks(); throw new Error(`Recursion detected when importing '${recursiveChunk2}' in '${caller}'.`); } return source.trim().replace(/(\r\n|\r|\n){3,}/g, `$1 `); } async function loadShader_default(source, shader, options) { const { minify, ...config } = options; resetSavedChunks(); let output = loadChunks(source, shader, config); output = minify ? removeSourceComments(output, true) : output; return { dependentChunks, outputShader: minify ? typeof minify !== "function" ? minifyShader(output) : await minify(output) : output }; } // src/index.ts function src_default({ include: include2 = /\.(glsl|wgsl|vert|frag|vs|fs)$/, removeDuplicatedImports = false, warnDuplicatedImports = true, defaultExtension = "glsl", importKeyword = "#include", minify = false, root = "/" } = {}) { return { name: "bun-plugin-glsl", setup(build) { build.onLoad({ filter: include2 }, async (args) => { const source = await Bun.file(args.path).text(); const { outputShader } = await loadShader_default(source, args.path, { removeDuplicatedImports, warnDuplicatedImports, defaultExtension, importKeyword, minify, root }); return { exports: { default: outputShader }, loader: "object" }; }); } }; } export { src_default as default };