diff --git a/viewer/glsl-import-tracker-loader.cjs b/viewer/glsl-import-tracker-loader.cjs new file mode 100644 index 0000000000..391ae1b500 --- /dev/null +++ b/viewer/glsl-import-tracker-loader.cjs @@ -0,0 +1,83 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * Helper to recursively finds all unique GLSL `import` dependencies in a file. + * @param {string} filePath - Absolute path to the file. + * @param {Set} visited - Set of already visited files. + * @returns {Set} - Set of all unique dependency file paths. + * @throws {Error} - If infinite recursion (circular dependency) is detected. + */ +function findAllDependencies(filePath, visited = new Set()) { + if (visited.has(filePath)) { + throw new Error(`Circular dependency detected in GLSL imports: ${filePath}`); + } + visited.add(filePath); + const deps = new Set(); + let content; + try { + content = fs.readFileSync(filePath, 'utf8'); + } catch (err) { + throw new Error(`Failed to read GLSL file: ${filePath}`); + } + const importRegex = /#pragma\s+glslify:\s+import\(['"](.+?)['"]\)/g; + let match; + const baseDir = path.dirname(filePath); + while ((match = importRegex.exec(content)) !== null) { + const importPath = match[1]; + const resolvedPath = path.resolve(baseDir, importPath); + if (!fs.existsSync(resolvedPath)) { + throw new Error(`GLSL import file not found: ${resolvedPath} (imported in ${filePath})`); + } + if (!deps.has(resolvedPath)) { + deps.add(resolvedPath); + // Recursively find dependencies in the imported file + for (const dep of findAllDependencies(resolvedPath, visited)) { + deps.add(dep); + } + } + } + visited.delete(filePath); + return deps; +} + +/** + * GLSL Loader for Webpack to track #pragma glslify: import dependencies. + * This allows Webpack to watch dependency files for changes during development. + * + * Usage: In webpack.config.js, use this loader before 'glslify-loader': + * { + * test: /\.(glsl|vert|frag)$/, + * type: 'asset/source', + * use: [ + * path.resolve(__dirname + '/glsl-import-tracker-loader.js'), + * { + * loader: 'glslify-loader', + * options: { + * transform: ['glslify-import'] + * } + * } + * ] + * } + * + * @param {string} source - The source code of the GLSL file. + * @returns {string} - The unchanged source code. + */ +module.exports = function (source) { + // 'this' is the loader context + const resourcePath = this.resourcePath; + let dependencies; + try { + dependencies = findAllDependencies(resourcePath); + } catch (err) { + this.emitError(err); + // Optionally, you can throw to fail the build: + // throw err; + return source; + } + for (const dep of dependencies) { + this.addDependency(dep); + } + // Pass the source unchanged + return source; +}; diff --git a/viewer/packages/webpack.config.js b/viewer/packages/webpack.config.js index 3a05125f84..0a16c325e5 100644 --- a/viewer/packages/webpack.config.js +++ b/viewer/packages/webpack.config.js @@ -100,7 +100,19 @@ module.exports = env => { { test: /\.(glsl|vert|frag)$/, type: 'asset/source', - use: ['glslify-loader'] + use: [ + { + // Help Webpack track #pragma glslify: import dependencies for --watch mode + loader: path.resolve(`${__dirname}/../glsl-import-tracker-loader.cjs`) + }, + { + loader: 'glslify-loader', + options: { + // Enable glslify-import transform to handle #pragma glslify: import statements + transform: ['glslify-import'] + } + } + ] }, { test: /\.css$/, diff --git a/viewer/webpack.config.js b/viewer/webpack.config.js index 372eab75db..7d8af916ff 100644 --- a/viewer/webpack.config.js +++ b/viewer/webpack.config.js @@ -66,7 +66,19 @@ module.exports = env => { { test: /\.(glsl|vert|frag)$/, type: 'asset/source', - use: ['glslify-loader'] + use: [ + { + // Help Webpack track #pragma glslify: import dependencies for --watch mode + loader: path.resolve(`${__dirname}/glsl-import-tracker-loader.cjs`) + }, + { + loader: 'glslify-loader', + options: { + // Enable glslify-import transform to handle #pragma glslify: import statements + transform: ['glslify-import'] + } + } + ] }, { test: /\.css$/,