├── .gitignore ├── LICENSE.md ├── README.md ├── example └── example.js ├── glslify-babel.js ├── lib ├── exec-glslify.js └── glslify-sync-hack.js ├── package.json └── test ├── fixtures ├── dirname.js ├── es6import.js ├── foo.glsl ├── inline.js ├── require.js ├── tagged.js └── transforms.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | ./bundle.js 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | * Copyright © 2015 [stackgl](http://github.com/stackgl/) contributors 5 | 6 | *stackgl contributors listed at * 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of 9 | this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | babel-plugin-glslify 2 | ==================== 3 | A [babel](https://babeljs.io/) transform for [glslify](https://github.com/stackgl/glslify). 4 | 5 | # Example 6 | This module works the same as the browserify transform in [glslify](https://github.com/stackgl/glslify), except that it is compatible with babel. It also supports ES6 syntax and some more advanced features like tagged strings. For example, you can write something like this, 7 | 8 | ```javascript 9 | import glsl from "glslify"; 10 | 11 | const myFragShader = glsl` 12 | #pragma glslify: noise = require(glsl-noise/simplex/2d) 13 | 14 | void main () { 15 | float brightness = noise(gl_FragCoord.xy); 16 | gl_FragColor = vec4(vec3(brightness), 1.); 17 | } 18 | `; 19 | ``` 20 | 21 | # Configuration 22 | To install this module, you need to install glslify as well: 23 | 24 | ``` 25 | npm i -S glslify babel-plugin-glslify 26 | ``` 27 | 28 | Then you need to configure babel to run the transform. For example, if you were using browserify with babelify to run babel you would add the following to your package.json to run glslify: 29 | 30 | ```javascript 31 | // ... in your package.json 32 | "browserify": { 33 | "transform": [[ 34 | "babelify", { 35 | "presets": ["es2015"], 36 | "plugins": ["glslify"] 37 | } 38 | ]] 39 | } 40 | ``` 41 | 42 | # License 43 | (c) 2016 MIT License 44 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | import glsl from 'glslify'; 2 | 3 | const myFragShader = glsl` 4 | #pragma glslify: noise = require(glsl-noise/simplex/2d) 5 | 6 | void main () { 7 | float brightness = noise(gl_FragCoord.xy); 8 | gl_FragColor = vec4(vec3(brightness), 1.); 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /glslify-babel.js: -------------------------------------------------------------------------------- 1 | var dirname = require('path').dirname 2 | var glslifyHack = require('./lib/glslify-sync-hack') 3 | 4 | module.exports = function (babel) { 5 | function resolveImport (identifier, declaration) { 6 | if (declaration.type === 'ImportDeclaration' && 7 | declaration.source && 8 | declaration.source.type === 'StringLiteral') { 9 | return declaration.source.value 10 | } 11 | return '' 12 | } 13 | 14 | function resolveRequire (path, identifier, declaration) { 15 | if (declaration.type === 'VariableDeclaration') { 16 | var children = declaration.declarations 17 | for (var i = 0; i < children.length; ++i) { 18 | if (children[i].id === identifier) { 19 | var rhs = children[i].init 20 | if (rhs && 21 | rhs.type === 'CallExpression' && 22 | rhs.callee.type === 'Identifier' && 23 | rhs.callee.name === 'require' && 24 | !path.scope.getBinding('require') && 25 | rhs.arguments.length === 1 && 26 | rhs.arguments[0].type === 'StringLiteral') { 27 | return rhs.arguments[0].value 28 | } 29 | } 30 | } 31 | } 32 | return '' 33 | } 34 | 35 | function resolveModule (path, name) { 36 | var binding = path.scope.getBinding(name) 37 | if (!binding) { 38 | return '' 39 | } 40 | if (!binding.constant) { 41 | return '' 42 | } 43 | switch (binding.kind) { 44 | case 'module': 45 | return resolveImport(binding.identifier, binding.path.parent) 46 | 47 | case 'var': 48 | case 'let': 49 | case 'const': 50 | return resolveRequire(path, binding.identifier, binding.path.parent) 51 | 52 | default: 53 | return '' 54 | } 55 | } 56 | 57 | function evalConstant (env, path, expression) { 58 | if (!expression) { 59 | throw new Error('glslify-binding: invalid expression') 60 | } 61 | switch (expression.type) { 62 | case 'StringLiteral': 63 | case 'BooleanLiteral': 64 | case 'NumericLiteral': 65 | case 'NullLiteral': 66 | return expression.value 67 | case 'TemplateLiteral': 68 | var quasis = expression.quasis 69 | return expression.expressions.reduce(function (prev, expr, i) { 70 | prev.push( 71 | evalConstant(env, path, expr), 72 | quasis[i].value.cooked) 73 | return prev 74 | }, [quasis[0].value.cooked]).join('') 75 | case 'Identifier': 76 | // TODO handle __dirname and other constants here 77 | var binding = path.scope.getBinding(expression.name) 78 | if (!binding) { 79 | if (expression.name === '__dirname') { 80 | return env.cwd 81 | } 82 | } 83 | throw new Error('glslify-babel: cannot resolve glslify(), unknown id') 84 | case 'UnaryExpression': 85 | var value = evalConstant(env, path, expression.argument) 86 | switch (expression.operator) { 87 | case '+': return +value 88 | case '-': return -value 89 | case '!': return !value 90 | case '~': return ~value 91 | case 'typeof': return typeof value 92 | case 'void': return void value 93 | } 94 | throw new Error('glslify-babel: unsupported unary operator') 95 | case 'BinaryExpression': 96 | var left = evalConstant(env, path, expression.left) 97 | var right = evalConstant(env, path, expression.right) 98 | switch (expression.operator) { 99 | case '+': return left + right 100 | case '-': return left - right 101 | case '*': return left * right 102 | case '&': return left & right 103 | case '/': return left / right 104 | case '%': return left % right 105 | case '|': return left | right 106 | case '^': return left ^ right 107 | case '||': return left || right 108 | case '&&': return left && right 109 | case '<<': return left << right 110 | case '>>': return left >> right 111 | case '>>>': return left >>> right 112 | case '===': return left === right 113 | case '!==': return left !== right 114 | case '<': return left < right 115 | case '>': return right < left 116 | case '<=': return left <= right 117 | case '>=': return left >= right 118 | } 119 | throw new Error('glslify-babel: unsupported binary expression') 120 | case 'ObjectExpression': 121 | return expression.properties.reduce(function (result, property) { 122 | if (property.type !== 'ObjectProperty') { 123 | throw new Error('glslify-babel: expected object property') 124 | } 125 | 126 | var value = evalConstant(env, path, property.value) 127 | var key = property.key 128 | if (key.type === 'Identifier') { 129 | result[key.name] = value 130 | } else if (key.type === 'StringLiteral') { 131 | result[key.value] = value 132 | } else { 133 | throw new Error('glslify-babel: invalid property type') 134 | } 135 | return result 136 | }, {}) 137 | case 'ArrayExpression': 138 | return expression.elements.map(function (prop) { 139 | if (!prop) { 140 | return void 0 141 | } 142 | return evalConstant(env, path, prop) 143 | }) 144 | default: 145 | throw new Error('glslify-babel: cannot resolve glslify() call') 146 | } 147 | } 148 | 149 | return { 150 | visitor: { 151 | TaggedTemplateExpression: function (path, state) { 152 | var node = path.node 153 | var tag = node.tag 154 | if (tag.type !== 'Identifier' || 155 | resolveModule(path, tag.name) !== 'glslify') { 156 | return 157 | } 158 | 159 | var filename = state.file.log.filename 160 | var cwd = dirname(filename) 161 | var env = { 162 | cwd: cwd 163 | } 164 | 165 | var stringInput = evalConstant(env, path, node.quasi) 166 | if (typeof stringInput !== 'string') { 167 | throw new Error('glslify-babel: invalid string template') 168 | } 169 | 170 | var result = glslifyHack(cwd, stringInput, { inline: true }) 171 | path.replaceWith(babel.types.stringLiteral(result)) 172 | }, 173 | 174 | CallExpression: function (path, state) { 175 | var node = path.node 176 | var callee = node.callee 177 | if (callee.type !== 'Identifier' || 178 | resolveModule(path, callee.name) !== 'glslify' || 179 | node.arguments.length < 1) { 180 | return 181 | } 182 | 183 | var filename = state.file.log.filename 184 | var cwd = dirname(filename) 185 | var env = { 186 | cwd: cwd 187 | } 188 | 189 | var stringInput = evalConstant(env, path, node.arguments[0]) 190 | if (typeof stringInput !== 'string') { 191 | throw new Error('glslify-babel: first argument must be a string') 192 | } 193 | 194 | var optionInput = {} 195 | if (node.arguments.length >= 2) { 196 | optionInput = evalConstant(env, path, node.arguments[1]) 197 | } 198 | if (typeof optionInput !== 'object' || !optionInput) { 199 | throw new Error('glslify-babel: invalid option input') 200 | } 201 | 202 | var result = glslifyHack(cwd, stringInput, optionInput) 203 | path.replaceWith(babel.types.stringLiteral(result)) 204 | } 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/exec-glslify.js: -------------------------------------------------------------------------------- 1 | var glslifyBundle = require('glslify-bundle') 2 | var glslifyDeps = require('glslify-deps') 3 | var bl = require('bl') 4 | 5 | process.stdin.pipe(bl(function (err, src) { 6 | if (err) throw err 7 | 8 | var input = JSON.parse(src.toString()) 9 | 10 | var depper = glslifyDeps() 11 | var postTransforms = [] 12 | 13 | function applyTransform (transform) { 14 | if (!Array.isArray(transform)) { 15 | transform = [transform] 16 | } 17 | var name = transform[0] 18 | var opts = transform[1] || {} 19 | if (opts.post) { 20 | postTransforms.push([name, opts]) 21 | } else { 22 | depper.transform(name, opts) 23 | } 24 | } 25 | 26 | // Handle transforms from options 27 | input.transforms.forEach(applyTransform) 28 | 29 | // TODO: Handle transforms from package.json 30 | 31 | // Call depper 32 | if (input.filename) { 33 | depper.add(input.filename, output) 34 | } else { 35 | depper.inline(input.data, input.baseDir, output) 36 | } 37 | 38 | // Apply post transforms, write result to stdout when finished 39 | function output (err, tree) { 40 | if (err) throw err 41 | next(err, String(glslifyBundle(tree))) 42 | } 43 | 44 | function next (err, src) { 45 | if (err) throw err 46 | var tr = postTransforms.shift() 47 | if (tr) { 48 | require(tr[0])(null, src, tr[1], next) 49 | } else { 50 | process.stdout.write(src) 51 | } 52 | } 53 | })) 54 | -------------------------------------------------------------------------------- /lib/glslify-sync-hack.js: -------------------------------------------------------------------------------- 1 | var execFileSync = require('child_process').execFileSync 2 | var path = require('path') 3 | 4 | var SCRIPT_PATH = path.join(__dirname, 'exec-glslify') 5 | 6 | module.exports = function (baseDir, stringInput, optionInput) { 7 | var transforms = optionInput.transform || optionInput.transforms || [] 8 | if (!Array.isArray(transforms)) { 9 | transforms = [transforms] 10 | } 11 | 12 | var glslifyInput = { 13 | transforms: transforms, 14 | baseDir: path.resolve(baseDir) 15 | } 16 | 17 | if (optionInput.inline) { 18 | glslifyInput.data = stringInput 19 | } else 20 | if ( stringInput.indexOf( '.' ) === 0 ){ 21 | glslifyInput.filename = path.resolve(baseDir,stringInput) 22 | } else{ 23 | glslifyInput.filename = path.resolve('node_modules', stringInput) 24 | } 25 | 26 | var options = { 27 | cwd: baseDir, 28 | input: JSON.stringify(glslifyInput) 29 | } 30 | 31 | return execFileSync(process.argv[0], [SCRIPT_PATH], options).toString() 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-glslify", 3 | "version": "2.0.0", 4 | "description": "glslify as a babel transform", 5 | "main": "glslify-babel.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "engines": { 10 | "node": ">=0.12" 11 | }, 12 | "dependencies": { 13 | "bl": "^1.1.2", 14 | "glslify-bundle": "^5.0.0", 15 | "glslify-deps": "^1.2.5" 16 | }, 17 | "devDependencies": { 18 | "babel-core": "^6.7.7", 19 | "babel-preset-es2015": "^6.6.0", 20 | "glslify": "^5.0.2", 21 | "glslify-hex": "^2.0.1", 22 | "tape": "^4.5.1" 23 | }, 24 | "scripts": { 25 | "test": "tape test/index.js" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/mikolalysenko/glslify-babel.git" 30 | }, 31 | "keywords": [ 32 | "babel", 33 | "glslify", 34 | "stackgl", 35 | "glsl", 36 | "webgl" 37 | ], 38 | "author": "Mikola Lysenko", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/mikolalysenko/glslify-babel/issues" 42 | }, 43 | "homepage": "https://github.com/mikolalysenko/glslify-babel#readme" 44 | } 45 | -------------------------------------------------------------------------------- /test/fixtures/dirname.js: -------------------------------------------------------------------------------- 1 | import blarp from 'glslify' 2 | 3 | console.log(blarp(__dirname + '/foo.glsl')) 4 | -------------------------------------------------------------------------------- /test/fixtures/es6import.js: -------------------------------------------------------------------------------- 1 | import blarp from 'glslify' 2 | 3 | console.log(blarp('foo.glsl')) 4 | -------------------------------------------------------------------------------- /test/fixtures/foo.glsl: -------------------------------------------------------------------------------- 1 | void main () { 2 | gl_FragColor = vec4(1, 0, 0, 1); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/inline.js: -------------------------------------------------------------------------------- 1 | import glslify from 'glslify' 2 | 3 | console.log(glslify(` 4 | void main () { 5 | gl_FragColor = vec4(1, 0, 0, 1); 6 | } 7 | `, { 8 | inline: true 9 | })) 10 | -------------------------------------------------------------------------------- /test/fixtures/require.js: -------------------------------------------------------------------------------- 1 | var foo = require('glslify') 2 | 3 | console.log(foo('foo.glsl')) 4 | -------------------------------------------------------------------------------- /test/fixtures/tagged.js: -------------------------------------------------------------------------------- 1 | import glsl from 'glslify' 2 | 3 | console.log(glsl` 4 | void main () { 5 | gl_FragColor = vec4(1, 0, 0, 1); 6 | }`) 7 | -------------------------------------------------------------------------------- /test/fixtures/transforms.js: -------------------------------------------------------------------------------- 1 | import glsl from 'glslify' 2 | 3 | console.log(glsl(` 4 | void main () { 5 | gl_FragColor = #ff0000; 6 | }`, { 7 | inline: true, 8 | transform: ['glslify-hex'] 9 | })) 10 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var path = require('path') 3 | var babel = require('babel-core') 4 | var glslifyBabel = require('../glslify-babel') 5 | var fs = require('fs') 6 | 7 | tape('glslify-babel', function (t) { 8 | function runTestCase (fixture) { 9 | var filename = path.join(__dirname, 'fixtures', fixture) 10 | var source = fs.readFileSync(filename) 11 | var transformed = babel.transform(source, { 12 | filename: filename, 13 | presets: [ 14 | 'es2015' 15 | ], 16 | plugins: [ 17 | glslifyBabel 18 | ] 19 | }) 20 | 21 | // FIXME need to diff outputs here 22 | console.log(fixture, '\n-----------------\n\n', transformed.code, '\n\n') 23 | } 24 | 25 | runTestCase('transforms.js') 26 | runTestCase('require.js') 27 | runTestCase('es6import.js') 28 | runTestCase('inline.js') 29 | runTestCase('tagged.js') 30 | runTestCase('dirname.js') 31 | 32 | t.end() 33 | }) 34 | --------------------------------------------------------------------------------