wgsl-plus
Version:
A WGSL preprocessor, prettifier, minifier, obfuscator, and compiler with C-style macros, conditional compilation, file linking, and multi-format output for WebGPU shaders.
140 lines (124 loc) • 5 kB
text/typescript
import { program } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import prettify from './tools/prettify';
import minify from './tools/minify';
import obfuscate from './tools/obfuscation/obfuscate';
import link from './tools/link';
import generateOutput from './tools/generate-output';
import preprocessWgsl from './tools/preprocessing/preprocess-wgsl';
const VERSION = '1.0.1';
/**
* Removes #binding directives from WGSL code.
* @param code The WGSL source code as a string.
* @returns The code with all #binding directives removed.
*/
function removeBindingDirectives(code: string): string {
// Split the code into lines
const lines = code.split('\n');
// Filter out lines that match the #binding directive pattern
const filteredLines = lines.filter(line =>
!/^\s*#binding\s+"[^"]+"\s*(\/\/.*)?$/.test(line)
&& !/^\s*#entrypoint\s+"[^"]+"\s*(\/\/.*)?$/.test(line)
);
// Rejoin the remaining lines
return filteredLines.join('\n');
}
// CLI setup
program
.name('wgsl-plus')
.description('A WGSL compiler with linking and multi-format output')
.version(VERSION)
.arguments('<importFiles...>')
.requiredOption('-o, --output <path>', 'Specify the output file path')
.option('-t, --export-type <type>', 'Export type for .js outputs (esm or commonjs)')
.option('-b, --obfuscate', 'Obfuscate variable names in the output')
.option('-m, --minify', 'Minify the output')
.option('-p, --prettify', 'Prettify the output')
.action((importFiles: string[], options: {
output: string;
exportType?: string,
obfuscate?: boolean,
minify?: boolean,
prettify?: boolean
}) => {
try {
let outputPath = "";
{ // Validation
// Validate import files
for (const file of importFiles) {
if (!fs.existsSync(file)) {
throw new Error(`Import file not found: ${file}`);
}
if (path.extname(file) !== '.wgsl') {
throw new Error(`Import file must be .wgsl: ${file}`);
}
}
// Validate output file
outputPath = path.resolve(options.output);
const outputExt = path.extname(outputPath);
const importPaths = importFiles.map(file => path.resolve(file));
if (importPaths.includes(outputPath)) {
throw new Error(`Output file cannot be one of the import files: ${options.output}`);
}
if (!['.wgsl', '.js', '.ts'].includes(outputExt)) {
throw new Error(`Output file must be .wgsl, .js, or .ts: ${options.output}`);
}
// Validate export-type flag
if (outputExt === '.js') {
if (!options.exportType) {
// Default to ESM.
options.exportType = 'esm';
}
if (!['esm', 'commonjs'].includes(options.exportType)) {
throw new Error(`Invalid export type: ${options.exportType}. Must be 'esm' or 'commonjs'`);
}
}
else{
if (options.exportType) {
throw new Error('Export type should not be specified for non-js outputs');
}
}
// Make sure only one of --obfuscate, --minify, or --prettify is used
const transformFlags = ['obfuscate', 'minify', 'prettify'];
const usedTransformFlags = transformFlags.filter(flag => options[flag]);
if (usedTransformFlags.length > 1) {
throw new Error(`Only one of ${transformFlags.join(', ')} can be used`);
}
}
let outputContent = "";
{ // Process all import files
outputContent = link(importFiles);
// Handle define statements.
outputContent = preprocessWgsl(outputContent);
// Remove the binding directives if obfuscate mode isn't being used.
if(!options.obfuscate) outputContent = removeBindingDirectives(outputContent);
if(options.prettify) outputContent = prettify(outputContent);
else if(options.minify) outputContent = minify(outputContent);
else if(options.obfuscate) outputContent = obfuscate(outputContent);
}
{ // Generate and write output
const finalOutput = generateOutput(options.output, outputContent, options.exportType);
const outputDir = path.dirname(outputPath);
fs.mkdirSync(outputDir, { recursive: true });
fs.writeFileSync(outputPath, finalOutput);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
});
// Additional help text
program.on('--help', () => {
console.log('\nExamples:');
console.log(' npx wgsl-plus input.wgsl -o output.wgsl');
console.log(' npx wgsl-plus a.wgsl b.wgsl -o output.js --export-type esm');
console.log(' npx wgsl-plus input.wgsl -o output.wgsl --obfuscate');
console.log('\nNotes:');
console.log(' • Input files must be .wgsl files.');
console.log(' • Output file must be .wgsl, .js, or .ts.');
console.log(' • --export-type is optional for .js outputs. Use commonjs or esm. Default is esm.');
console.log(' • Use one or none of --obfuscate, --prettify, or --minify to transform output.');
});
program.parse();