@threeify/shader-transpiler
Version:
A glsl to JS module transpiler.
91 lines • 4.26 kB
JavaScript
import fs from 'node:fs';
import path from 'node:path';
import makeDir from 'make-dir';
import { stripComments, stripUnnecessaryLineEndings, stripUnnecessarySpaces } from './minification.js';
const includeLocalRegex = /^[\t ]*#(?:pragma +)?include +"([\w./]+)"/gm;
const includeAbsoluteRegex = /^[\t ]*#(?:pragma +)?include +<([\w./]+)>/gm;
const jsModulePrefix = 'export default /* glsl */ `\n';
const jsModulePostfix = '`;\n';
export function glslToJavaScriptTranspiler(sourceFileName, outputFileName, options) {
const sourcePath = path.dirname(sourceFileName);
const sourceCode = fs.readFileSync(sourceFileName, 'utf8');
const includeGuardName = sourceFileName
.replace(options.rootDir, '')
.replace(/[./_]/gm, '_');
const includeImports = [];
const errors = [];
const searchExtensions = options.extensions.map((extension) => '.' + extension);
searchExtensions.push('');
if (options.allowJSIncludes) {
searchExtensions.slice(0).forEach((extension) => {
searchExtensions.push(extension + '.ts', extension + '.js');
});
}
function includeReplacer(searchDirectories) {
return function (match, sourceFileName) {
if (!sourceFileName)
return '';
const directories = searchDirectories.slice(0);
const pathsAttempted = [];
let includeFilePath = '';
directories.forEach((directory) => {
const testIncludeFilePath = path.normalize(path.join(directory, sourceFileName));
searchExtensions.forEach((extension) => {
const test2IncludeFilePath = testIncludeFilePath + extension;
pathsAttempted.push(test2IncludeFilePath);
if (fs.existsSync(test2IncludeFilePath)) {
includeFilePath = test2IncludeFilePath;
}
});
});
if (includeFilePath.length === 0) {
const errorMsg = `Could not resolve "${match}" - current directory ${sourcePath}, attempts: ${pathsAttempted.join(',')}`;
errors.push(errorMsg);
return errorMsg;
}
const includeVar = includeFilePath
.replace(options.rootDir, '')
.replace(/[./_]/gm, '_');
let relativeIncludePath = path.relative(sourcePath, includeFilePath);
if (relativeIncludePath.indexOf('.') !== 0) {
relativeIncludePath = './' + relativeIncludePath;
}
relativeIncludePath = relativeIncludePath.replace(/.js$/, '');
const includeImport = `import ${includeVar} from '${relativeIncludePath}.js';`;
if (!includeImports.includes(includeImport)) {
includeImports.push(includeImport);
}
const result = '${' + includeVar + '}';
return result;
};
}
let outputSource = sourceCode;
if (options.minify) {
outputSource = stripComments(outputSource);
outputSource = stripUnnecessaryLineEndings(outputSource);
outputSource = stripUnnecessarySpaces(outputSource);
}
if (sourceCode.includes('#pragma once')) {
const includeGuardPrefix = `#ifndef ${includeGuardName}\n#define ${includeGuardName}\n`;
const includeGuardPostfix = `\n\n#endif // end of include guard`;
outputSource =
includeGuardPrefix +
outputSource.replace(/#pragma once/gm, '') +
'\n' +
includeGuardPostfix;
}
outputSource = outputSource.replace(includeLocalRegex, includeReplacer([sourcePath]));
outputSource = outputSource.replace(includeAbsoluteRegex, includeReplacer([options.rootDir].concat(options.includeDirs)));
let outputModule = includeImports.join('\n');
if (outputModule.length > 0) {
outputModule += '\n\n';
}
outputModule += jsModulePrefix + outputSource + '\n' + jsModulePostfix;
const outputPath = path.dirname(outputFileName);
if (!fs.existsSync(outputPath)) {
makeDir.sync(outputPath);
}
fs.writeFileSync(outputFileName, outputModule);
return errors;
}
//# sourceMappingURL=transpiler.js.map