├── .eslintrc ├── .gitignore ├── .prettierrc.js ├── .travis.yml ├── README.md ├── bin └── cli.js ├── package.json ├── renovate.json ├── src ├── glslify-bundle.ts ├── glslify-import.ts ├── glslify.ts ├── index.ts ├── tokens-to-string.ts └── topo-sort.ts ├── test ├── _util.ts ├── cli.test.ts ├── export.test.ts ├── fixtures │ ├── hex.glsl │ ├── import-1.glsl │ ├── import-2.glsl │ ├── import-3.glsl │ ├── import-entry.glsl │ ├── include-1.glsl │ ├── include-2.glsl │ ├── include-3.glsl │ ├── include-entry.glsl │ ├── nest-conflict-1.glsl │ ├── nest-conflict-2.glsl │ ├── nest-conflict-entry.glsl │ └── test01.frag ├── import.test.ts ├── index.test.ts └── source-map.test.ts ├── tsconfig.json └── typings ├── core.d.ts ├── index.d.ts └── modules.d.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "jest/globals": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:prettier/recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "plugins": ["@typescript-eslint", "jest"], 12 | "parser": "@typescript-eslint/parser", 13 | "parserOptions": { 14 | "sourceType": "module", 15 | "project": "./tsconfig.json" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !node_modules 2 | ### https://raw.github.com/github/gitignore/6531e382923e8e4d89b069a045e1c2a5d4be6321/Node.gitignore 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | 82 | # I don't wanna add package lock to git 83 | package-lock.json 84 | 85 | lib/**/* 86 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4 3 | }; 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "lts/*" 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm ci 10 | - npm run ci 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glslify-lite 2 | 3 | [![](https://img.shields.io/travis/fand/glslify-lite.svg)](https://travis-ci.org/fand/glslify-lite) [![](https://img.shields.io/codecov/c/github/fand/glslify-lite.svg)](https://codecov.io/gh/fand/glslify-lite) ![](https://img.shields.io/npm/l/glslify-lite.svg) 4 | 5 | A fast, lightweight fork of [glslify](https://github.com/glslify/glslify). 6 | Intended to provide more useful APIs for linters, live coding apps, etc. 7 | 8 | ## Why? 9 | 10 | glslify is great, but has some problems especially in realtime usage such as linters, live coding, etc. 11 | 12 | - Synchronous, blocking API by design 13 | - No support for sourcemaps 14 | 15 | glslify-lite overcomes these problems. 16 | However, we don't provide completely same features as glslify. 17 | 18 | | | glslify | glslify-lite | 19 | | ----------------------- | :-----: | :-----------: | 20 | | API | Sync | Async | 21 | | Sourcemaps | - | ✅ | 22 | | Output code is clean | ✅ | - | 23 | | Transformer support | ✅ | Only built-in | 24 | | Tagged template literal | ✅ | - | 25 | | Browserify | ✅ | - | 26 | 27 | ## Install 28 | 29 | ``` 30 | npm i glslify-lite 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### CLI 36 | 37 | The CLI can take a file as its first argument, and output to a file using the -o flag: 38 | 39 | ``` 40 | glslify-lite index.glsl -o output.glsl 41 | ``` 42 | 43 | It can also read input from stdin and output to stdout: 44 | 45 | ``` 46 | cat index.glsl | glslify-lite > output.glsl 47 | ``` 48 | 49 | ### API 50 | 51 | #### glslify.compile(src, opts): Promise 52 | 53 | Compile a shader string from a string `src`. 54 | 55 | Optionally provide: 56 | 57 | - `opts.basedir` - directory to resolve relative paths in `src` 58 | 59 | #### glslify.file(filename, opts): Promise 60 | 61 | Compile a shader from a `filename`. 62 | 63 | Optionally provide: 64 | 65 | - `opts.basedir` - directory to resolve relative paths in `src` 66 | 67 | ## LICENSE 68 | 69 | MIT 70 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const meow = require("meow"); 5 | const getStdin = require("get-stdin"); 6 | const p = require("pify"); 7 | const glslify = require(".."); 8 | 9 | const cli = meow( 10 | ` 11 | Usage 12 | $ glslify-lite -o 13 | 14 | Options 15 | --output, -o Specify an output file to write your shader to. 16 | --version, -v Output version number. 17 | --help, -h Display this message. 18 | 19 | Examples 20 | Read index.glsl and write to output.glsl: 21 | $ glslify-lite index.glsl -o output.glsl 22 | 23 | Alternatively: 24 | $ cat index.glsl | glslify-lite > output.glsl 25 | `, 26 | { 27 | flags: { 28 | output: { 29 | type: "string", 30 | alias: "o" 31 | }, 32 | help: { 33 | type: "bool", 34 | alias: "h" 35 | }, 36 | version: { 37 | type: "bool", 38 | alias: "v" 39 | } 40 | } 41 | } 42 | ); 43 | 44 | if (cli.flags.help) { 45 | cli.showHelp(); 46 | process.exit(0); 47 | } 48 | 49 | if (cli.flags.version) { 50 | cli.showVersion(); 51 | process.exit(0); 52 | } 53 | 54 | const die = msg => { 55 | console.error(`ERROR: ${msg}`); 56 | process.exit(-1); 57 | }; 58 | 59 | (async () => { 60 | let shader; 61 | if (cli.input[0]) { 62 | const input = path.resolve(process.cwd(), cli.input[0]); 63 | shader = await glslify.file(input, { basedir: process.cwd() }); 64 | } else { 65 | const input = await getStdin(); 66 | if (input.trim() == "") { 67 | die("Input from STDIN is empty"); 68 | } 69 | shader = await glslify.compile(input, { basedir: process.cwd() }); 70 | } 71 | 72 | if (!shader) { 73 | die("Invalid output"); 74 | } 75 | 76 | if (cli.flags.output) { 77 | const output = cli.flags.output; 78 | await p(fs.writeFile)(output, shader, "utf8"); 79 | } else { 80 | console.log(shader); 81 | } 82 | })(); 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glslify-lite", 3 | "description": "A fast, lightweight fork of glslify", 4 | "version": "0.0.7", 5 | "author": "fand (https://gmork.in/)", 6 | "bin": { 7 | "glslify-lite": "./bin/cli.js" 8 | }, 9 | "bugs": "https://github.com/fand/glslify-lite/issues", 10 | "dependencies": { 11 | "convert-source-map": "^1.6.0", 12 | "get-stdin": "^7.0.0", 13 | "glsl-inject-defines": "^1.0.3", 14 | "glsl-token-defines": "^1.0.0", 15 | "glsl-token-depth": "^1.1.2", 16 | "glsl-token-descope": "^1.0.2", 17 | "glsl-token-scope": "^1.1.2", 18 | "glsl-token-string": "^1.0.1", 19 | "glsl-token-whitespace-trim": "^1.0.0", 20 | "glsl-tokenizer": "^2.1.5", 21 | "glslify-deps": "^1.3.1", 22 | "meow": "^5.0.0", 23 | "murmurhash-js": "^1.0.0", 24 | "npm-run-all": "^4.1.5", 25 | "pify": "^5.0.0", 26 | "shallow-copy": "0.0.1", 27 | "source-map": "^0.8.0-beta.0", 28 | "stack-trace": "0.0.10" 29 | }, 30 | "devDependencies": { 31 | "@types/convert-source-map": "1.5.1", 32 | "@types/jest": "25.2.1", 33 | "@types/murmurhash-js": "1.0.3", 34 | "@types/node": "12.12.25", 35 | "@types/pify": "3.0.2", 36 | "@types/source-map": "0.5.7", 37 | "@types/stack-trace": "0.0.29", 38 | "@types/tmp": "0.1.0", 39 | "@typescript-eslint/eslint-plugin": "1.13.0", 40 | "@typescript-eslint/parser": "1.13.0", 41 | "codecov": "3.6.1", 42 | "eslint": "6.8.0", 43 | "eslint-config-prettier": "6.9.0", 44 | "eslint-plugin-jest": "22.21.0", 45 | "eslint-plugin-prettier": "3.1.2", 46 | "glsl-noise": "0.0.0", 47 | "husky": "4.2.3", 48 | "jest": "24.9.0", 49 | "lint-staged": "10.0.8", 50 | "prettier": "1.19.1", 51 | "tmp": "0.1.0", 52 | "ts-jest": "25.2.1", 53 | "typescript": "3.7.5" 54 | }, 55 | "files": [ 56 | "README.md", 57 | "package.json", 58 | "package-lock.json", 59 | "lib", 60 | "bin" 61 | ], 62 | "homepage": "https://github.com/fand/glslify-lite", 63 | "husky": { 64 | "hooks": { 65 | "pre-commit": "lint-staged" 66 | } 67 | }, 68 | "jest": { 69 | "transform": { 70 | "^.+\\.ts$": "ts-jest" 71 | }, 72 | "testRegex": "/test/.*\\.test\\.ts$", 73 | "moduleFileExtensions": [ 74 | "ts", 75 | "js", 76 | "json", 77 | "node" 78 | ], 79 | "coveragePathIgnorePatterns": [ 80 | "/lib/", 81 | "/test/", 82 | "/node_modules/" 83 | ] 84 | }, 85 | "keywords": [], 86 | "license": "MIT", 87 | "lint-staged": { 88 | "*.{js,ts,json,md}": [ 89 | "prettier --write", 90 | "git add" 91 | ] 92 | }, 93 | "main": "lib/index.js", 94 | "repository": "https://github.com/fand/glslify-lite", 95 | "scripts": { 96 | "build": "tsc -d", 97 | "ci": "run-p lint build test && codecov", 98 | "lint": "eslint src/**/*.ts", 99 | "test": "jest --coverage" 100 | }, 101 | "types": "lib/index.d.ts" 102 | } 103 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "labels": ["renovate"], 4 | "automerge": true, 5 | "major": { 6 | "automerge": false 7 | }, 8 | "minor": { 9 | "groupName": "minor dependencies" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/glslify-bundle.ts: -------------------------------------------------------------------------------- 1 | import hash from "murmurhash-js/murmurhash3_gc"; 2 | import tokenize = require("glsl-tokenizer/string"); 3 | import descope = require("glsl-token-descope"); 4 | import defines = require("glsl-token-defines"); 5 | import scope = require("glsl-token-scope"); 6 | import depth = require("glsl-token-depth"); 7 | import copy = require("shallow-copy"); 8 | import sourceMap from "source-map"; 9 | import convert from "convert-source-map"; 10 | import topoSort from "./topo-sort"; 11 | import tokensToString from "./tokens-to-string"; 12 | import gImport from "./glslify-import"; 13 | 14 | function glslifyPreprocessor(data: string): boolean { 15 | return /#pragma glslify:/.test(data); 16 | } 17 | 18 | function glslifyExport(data: string): RegExpExecArray | null { 19 | return /#pragma glslify:\s*export\(([^)]+)\)/.exec(data); 20 | } 21 | 22 | function glslifyRequire(data: string): RegExpExecArray | null { 23 | return /#pragma glslify:\s*([^=\s]+)\s*=\s*require\(([^)]+)\)/.exec(data); 24 | } 25 | 26 | function indexById(deps: DepsInfo[]): DepsHash { 27 | return deps.reduce((hash: DepsHash, entry: DepsInfo): DepsHash => { 28 | hash[entry.id] = entry; 29 | return hash; 30 | }, {}); 31 | } 32 | 33 | interface Mappings { 34 | [key: string]: string | undefined; 35 | } 36 | 37 | type Binding = string[]; 38 | 39 | function toMapping(maps?: string[]): {} | false { 40 | if (!maps) return false; 41 | 42 | return maps.reduce((mapping: Mappings, defn): Mappings => { 43 | const defns = defn.split(/\s?=\s?/g); 44 | 45 | const expr = defns.pop(); 46 | 47 | defns.forEach((key): void => { 48 | mapping[key] = expr; 49 | }); 50 | 51 | return mapping; 52 | }, {}); 53 | } 54 | 55 | class Bundle { 56 | private deps: DepsInfo[]; 57 | private depIndex: DepsHash; 58 | 59 | public constructor(deps: DepsInfo[]) { 60 | // Reorder dependencies topologically 61 | this.deps = topoSort(deps); 62 | this.depIndex = indexById(this.deps); 63 | } 64 | 65 | public async bundleToString(): Promise { 66 | // Apply pre-transform for deps sources 67 | for (let i = 0; i < this.deps.length; i++) { 68 | await this.preTransform(this.deps[i]); 69 | } 70 | 71 | for (let i = 0; i < this.deps.length; i++) { 72 | await this.preprocess(this.deps[i]); 73 | } 74 | 75 | let tokens: Token[] = []; 76 | for (let i = 0; i < this.deps.length; i++) { 77 | if (this.deps[i].entry) { 78 | tokens = tokens.concat(this.bundle(this.deps[i])); 79 | } 80 | } 81 | 82 | // Just use bundled source code. 83 | // Original glslify cleans up and trims the tokens, but we don't need it. 84 | return tokensToString(tokens); 85 | } 86 | 87 | private async preTransform(dep: DepsInfo): Promise { 88 | dep.source = await gImport(dep.source, dep.file); // eslint-disable-line 89 | } 90 | 91 | /** 92 | * Parse DepsInfo[] and add 'parsed' field to them. 93 | * 'parsed' has following fields: 94 | * - tokens: Token[] of the file 95 | * - imports: identifiers the file imports from other files 96 | * - exports: identifiers the file exports 97 | */ 98 | private async preprocess(dep: DepsInfo): Promise { 99 | // Get sourcemaps created by pretransform 100 | const rawMap = convert.fromSource(dep.source); 101 | const consumer = rawMap 102 | ? await new sourceMap.SourceMapConsumer(rawMap.toObject()) 103 | : null; 104 | if (consumer) { 105 | dep.source = convert.removeComments(dep.source); // eslint-disable-line 106 | } 107 | 108 | const tokens = tokenize(dep.source); 109 | const imports = []; 110 | let exports = null; 111 | 112 | depth(tokens); 113 | scope(tokens); 114 | 115 | // Note: tokens must be sorted by position 116 | let lastLine = 1; 117 | let lastColumn = 1; 118 | 119 | for (let i = 0; i < tokens.length; i++) { 120 | const token = tokens[i]; 121 | 122 | token.source = dep.file; 123 | 124 | // Save original position. 125 | // Note: token.line and column is the end position of the token. 126 | token.original = { 127 | line: lastLine, 128 | column: lastColumn 129 | }; 130 | 131 | // Get original position from sourcemaps 132 | if (consumer) { 133 | const op = consumer.originalPositionFor({ 134 | line: lastLine, 135 | column: lastColumn 136 | }); 137 | if (op.line && op.column) { 138 | // preTransform saves column as offset of the position, 139 | // instead of the position itself 140 | token.original = { 141 | line: op.line, 142 | column: lastColumn - (op.column - 1) 143 | }; 144 | } 145 | if (op.source) { 146 | token.source = op.source; 147 | } 148 | } 149 | 150 | // Update last position 151 | if (token.type === "whitespace") { 152 | lastLine = token.line; 153 | lastColumn = token.column + 1; 154 | } else { 155 | lastColumn += token.data.length; 156 | } 157 | 158 | // console.log(token.original, token.data.length, token.type); 159 | if (token.type !== "preprocessor") continue; 160 | if (!glslifyPreprocessor(token.data)) continue; 161 | 162 | const exported = glslifyExport(token.data); 163 | const imported = glslifyRequire(token.data); 164 | 165 | if (exported) { 166 | exports = exported[1]; 167 | tokens.splice(i--, 1); // Delete this token 168 | } else if (imported) { 169 | const name = imported[1]; 170 | const maps = imported[2].split(/\s?,\s?/g); 171 | const map0 = maps.shift() as string; 172 | const path = map0 173 | .trim() 174 | .replace(/^'|'$/g, "") 175 | .replace(/^"|"$/g, ""); 176 | const target = this.depIndex[dep.deps[path]]; 177 | imports.push({ 178 | name: name, 179 | path: path, 180 | target: target, 181 | maps: toMapping(maps), 182 | index: i 183 | }); 184 | tokens.splice(i--, 1); // Delete this token 185 | } 186 | } 187 | 188 | const eof = tokens[tokens.length - 1]; 189 | if (eof && eof.type === "eof") { 190 | tokens.pop(); 191 | } 192 | 193 | if (dep.entry) { 194 | exports = exports || "main"; 195 | } 196 | 197 | if (!exports) { 198 | throw new Error(dep.file + " does not export any symbols"); 199 | } 200 | 201 | // eslint-disable-next-line 202 | dep.parsed = { 203 | tokens: tokens, 204 | imports: imports, 205 | exports: exports 206 | }; 207 | } 208 | 209 | /** 210 | * DepsInfo into Token[] 211 | * @param entry - An array of dependency entry returned from deps 212 | */ 213 | private bundle(entry: DepsInfo): Token[] { 214 | const resolved: { [name: string]: boolean } = {}; 215 | 216 | function resolve( 217 | dep: DepsInfo, 218 | bindings: string[][] 219 | ): [string, Token[]] { 220 | // Compute suffix for module 221 | bindings.sort(); 222 | const ident = bindings.join(":") + ":" + dep.id; 223 | let suffix = "_" + hash(ident); 224 | 225 | if (dep.entry) { 226 | suffix = ""; 227 | } 228 | 229 | const parsed = dep.parsed; 230 | if (!parsed) { 231 | throw "Dep is not preprocessed"; 232 | } 233 | 234 | // Test if export is already resolved 235 | const exportName = parsed.exports + suffix; 236 | if (resolved[exportName]) { 237 | return [exportName, []]; 238 | } 239 | 240 | // Initialize map for variable renamings based on bindings 241 | const rename: { [from: string]: string } = {}; 242 | for (let i = 0; i < bindings.length; ++i) { 243 | const binding = bindings[i]; 244 | rename[binding[0]] = binding[1]; 245 | } 246 | 247 | // Resolve all dependencies 248 | const imports = parsed.imports; 249 | const edits: [number, Token[]][] = []; 250 | for (let i = 0; i < imports.length; ++i) { 251 | const data = imports[i]; 252 | 253 | const importMaps = data.maps; 254 | const importName = data.name; 255 | const importTarget = data.target; 256 | 257 | const importBindings = Object.keys(importMaps).map( 258 | (id): Binding => { 259 | const value = importMaps[id]; 260 | 261 | // floats/ints should not be renamed 262 | if (value.match(/^\d+(?:\.\d+?)?$/g)) { 263 | return [id, value]; 264 | } 265 | 266 | // properties (uVec.x, ray.origin, ray.origin.xy etc.) should 267 | // have their host identifiers renamed 268 | const parent = value.match(/^([^.]+)\.(.+)$/); 269 | if (parent) { 270 | return [ 271 | id, 272 | (rename[parent[1]] || parent[1] + suffix) + 273 | "." + 274 | parent[2] 275 | ]; 276 | } 277 | 278 | return [id, rename[value] || value + suffix]; 279 | } 280 | ); 281 | 282 | const importTokens = resolve(importTarget, importBindings); 283 | rename[importName] = importTokens[0]; 284 | edits.push([data.index, importTokens[1]]); 285 | } 286 | 287 | // Rename tokens 288 | const parsedTokens = parsed.tokens.map(copy); 289 | const parsedDefs = defines(parsedTokens); 290 | let tokens = descope(parsedTokens, (local: number): string => { 291 | if (parsedDefs[local]) return local + ""; 292 | if (rename[local]) return rename[local] + ""; 293 | 294 | return local + suffix; 295 | }); 296 | 297 | // Insert edits to tokens 298 | // Sort edits by desc to avoid index mismatch 299 | edits.sort((a, b): number => { 300 | return b[0] - a[0]; 301 | }); 302 | for (let i = 0; i < edits.length; ++i) { 303 | const edit = edits[i]; 304 | tokens = tokens 305 | .slice(0, edit[0]) 306 | .concat(edit[1]) 307 | .concat(tokens.slice(edit[0])); 308 | } 309 | 310 | resolved[exportName] = true; 311 | return [exportName, tokens]; 312 | } 313 | 314 | const result = resolve(entry, [])[1]; 315 | return result; 316 | } 317 | } 318 | 319 | export default async function(deps: DepsInfo[]): Promise { 320 | // return inject(new Bundle(deps).src, { 321 | // GLSLIFY: 1 322 | // }); 323 | return await new Bundle(deps).bundleToString(); 324 | } 325 | -------------------------------------------------------------------------------- /src/glslify-import.ts: -------------------------------------------------------------------------------- 1 | import resolve = require("glsl-resolve"); 2 | import path = require("path"); 3 | import fs = require("fs"); 4 | import * as sourceMap from "source-map"; 5 | import * as convert from "convert-source-map"; 6 | import p from "pify"; 7 | 8 | function getImportPath(data: string): string | void { 9 | const m = /#pragma glslify:\s*import\(['"]?([^)]+?)['"]?\)/.exec(data); 10 | if (m && m[1]) return m[1]; 11 | } 12 | 13 | function getIncludePath(data: string): string | void { 14 | const m = /#include\s+"([^"]+)"/.exec(data); 15 | if (m && m[1]) return m[1]; 16 | } 17 | 18 | export default async (src: string, filepath: string): Promise => { 19 | const map = new sourceMap.SourceMapGenerator(); 20 | const basedir = path.dirname(filepath); 21 | 22 | const lines: { content: string; source: string; line: number }[] = []; 23 | 24 | const dig = async (str: string, file: string): Promise => { 25 | const strLines = str.split("\n"); 26 | for (let i = 0; i < strLines.length; i++) { 27 | const line = strLines[i]; 28 | let rawImportPath = getImportPath(line) || getIncludePath(line); 29 | 30 | if (rawImportPath) { 31 | // Append "./" so that glsl-resolve can find the file 32 | if (!rawImportPath.match(/^\.\//)) { 33 | rawImportPath = `./${rawImportPath}`; 34 | } 35 | 36 | const importPath = await p(resolve)(rawImportPath, { basedir }); 37 | const importFile = await p(fs.readFile)(importPath, "utf8"); 38 | await dig(importFile, importPath); 39 | } else { 40 | lines.push({ content: line, source: file, line: i + 1 }); 41 | } 42 | } 43 | }; 44 | 45 | await dig(src, filepath); 46 | 47 | let output = ""; 48 | lines.forEach((l, i: number): void => { 49 | output += l.content + "\n"; 50 | map.addMapping({ 51 | source: l.source, 52 | original: { 53 | line: l.line, 54 | column: 1 55 | }, 56 | generated: { 57 | line: i + 1, 58 | column: 1 59 | } 60 | }); 61 | }); 62 | 63 | const mapJSON = map.toString(); 64 | const mapComment = convert.fromJSON(mapJSON).toComment(); 65 | 66 | return output + "\n" + mapComment; 67 | }; 68 | -------------------------------------------------------------------------------- /src/glslify.ts: -------------------------------------------------------------------------------- 1 | import glslifyBundle from "./glslify-bundle"; 2 | import * as path from "path"; 3 | import * as stackTrace from "stack-trace"; 4 | import p from "pify"; 5 | import glslifyDeps = require("glslify-deps"); 6 | 7 | interface GlslifyOpts { 8 | basedir?: string; 9 | } 10 | 11 | class Glslifier { 12 | private basedir: string; 13 | 14 | public constructor() { 15 | try { 16 | // Get the filepath where module.exports.compile etc. was called 17 | this.basedir = path.dirname(stackTrace.get()[2].getFileName()); 18 | } catch (err) { 19 | this.basedir = process.cwd(); 20 | } 21 | } 22 | 23 | /** 24 | * Bundle the shader given as a string. 25 | * @param src - The content of the input shader 26 | */ 27 | public async compile(src: string, opts?: GlslifyOpts): Promise { 28 | if (!opts) { 29 | opts = {}; 30 | } 31 | const depper = this.createDepper(opts); 32 | const deps = await p(depper.inline.bind(depper))( 33 | src, 34 | opts.basedir || this.basedir 35 | ); 36 | 37 | return glslifyBundle(deps); 38 | } 39 | 40 | /** 41 | * Bundle the shader for given filename. 42 | * @param filename - The filepath of the input shader 43 | */ 44 | public async file(filename: string, opts?: GlslifyOpts): Promise { 45 | if (!opts) { 46 | opts = {}; 47 | } 48 | const depper = this.createDepper(opts); 49 | const deps = await p(depper.add.bind(depper))( 50 | path.resolve(opts.basedir || this.basedir, filename) 51 | ); 52 | 53 | return glslifyBundle(deps); 54 | } 55 | 56 | /** 57 | * Create depper for the basedir. 58 | */ 59 | private createDepper(opts?: GlslifyOpts): Depper { 60 | if (!opts) opts = {}; 61 | return glslifyDeps({ cwd: opts.basedir || this.basedir }); 62 | } 63 | } 64 | 65 | export function compile(src: string, opts?: GlslifyOpts): Promise { 66 | return new Glslifier().compile(src, opts); 67 | } 68 | 69 | export function file(file: string, opts?: GlslifyOpts): Promise { 70 | return new Glslifier().file(file, opts); 71 | } 72 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as glslify from "./glslify"; 2 | export const compile = glslify.compile; 3 | export const file = glslify.file; 4 | -------------------------------------------------------------------------------- /src/tokens-to-string.ts: -------------------------------------------------------------------------------- 1 | import * as sourceMap from "source-map"; 2 | import * as convert from "convert-source-map"; 3 | 4 | export default function tokensToString(tokens: Token[]): string { 5 | const output: string[] = []; 6 | const map = new sourceMap.SourceMapGenerator(); 7 | 8 | let line = 1; 9 | let column = 1; 10 | 11 | tokens.forEach((token): void => { 12 | if (token.type === "eof") return; 13 | 14 | output.push(token.data); 15 | 16 | const sourceFile = token.source; 17 | const originalPos = token.original; 18 | if (!sourceFile || !originalPos) { 19 | return; 20 | } 21 | 22 | const tokenMap = { 23 | source: sourceFile, 24 | original: { 25 | line: originalPos.line, 26 | column: originalPos.column 27 | }, 28 | generated: { 29 | line: line, 30 | column: column 31 | } 32 | }; 33 | map.addMapping(tokenMap); 34 | 35 | const lines = token.data.split(/\r\n|\r|\n/); 36 | if (lines.length > 1) { 37 | // if token has multiple lines 38 | line += lines.length - 1; 39 | column = lines[lines.length - 1].length + 1; 40 | } else { 41 | column += token.data.length; 42 | } 43 | }); 44 | 45 | const src = output.join(""); 46 | const mapJSON = map.toString(); 47 | const mapComment = convert.fromJSON(mapJSON).toComment(); 48 | 49 | return src + "\n" + mapComment; 50 | } 51 | -------------------------------------------------------------------------------- /src/topo-sort.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Permutes the dependencies into topological order. 3 | */ 4 | export default function topoSort(deps: DepsInfo[]): DepsInfo[] { 5 | // Build reversed adjacency list 6 | const adj: { [id: number]: number[] } = {}; 7 | const inDegree: { [id: number]: number } = {}; 8 | const index: { [id: number]: DepsInfo } = {}; 9 | deps.forEach((dep): void => { 10 | const v = dep.id; 11 | const nbhd = Object.keys(dep.deps); 12 | index[dep.id] = dep; 13 | inDegree[v] = nbhd.length; 14 | nbhd.forEach((filename): void => { 15 | const u = dep.deps[filename]; 16 | if (adj[u]) { 17 | adj[u].push(v); 18 | } else { 19 | adj[u] = [v]; 20 | } 21 | }); 22 | }); 23 | 24 | // Initialize toVisit queue 25 | const result: number[] = []; 26 | const inverse: { [id: number]: number } = {}; 27 | deps.forEach((dep): void => { 28 | const v = dep.id; 29 | if (!adj[v]) { 30 | adj[v] = []; 31 | } 32 | if (inDegree[v] === 0) { 33 | inverse[v] = result.length; 34 | result.push(v); 35 | } 36 | }); 37 | 38 | // Run BFS 39 | for (let ptr = 0; ptr < result.length; ptr++) { 40 | const v = result[ptr]; 41 | adj[v].forEach((u): void => { 42 | if (--inDegree[u] === 0) { 43 | inverse[u] = result.length; 44 | result.push(u); 45 | } 46 | }); 47 | } 48 | 49 | if (result.length !== deps.length) { 50 | throw new Error("cyclic dependency"); 51 | } 52 | 53 | // Relabel dependencies 54 | return result.map( 55 | (v): DepsInfo => { 56 | const dep = index[v]; 57 | const deps = dep.deps; 58 | const ndeps: { [filename: string]: number } = {}; 59 | Object.keys(deps).forEach((filename): void => { 60 | ndeps[filename] = inverse[deps[filename]] | 0; 61 | }); 62 | return { 63 | id: inverse[v] | 0, 64 | deps: ndeps, 65 | file: dep.file, 66 | source: dep.source, 67 | entry: dep.entry 68 | }; 69 | } 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /test/_util.ts: -------------------------------------------------------------------------------- 1 | import * as sourceMap from "source-map"; 2 | 3 | // Util: get original position 4 | export const getOriginalPos = ( 5 | src: string, 6 | pos: { line: number; column: number }, 7 | consumer: sourceMap.SourceMapConsumer 8 | ): MapPos | undefined => { 9 | // Try exact line 10 | const op = consumer.originalPositionFor(pos); 11 | if (op.line !== null) { 12 | return op as MapPos; 13 | } 14 | 15 | const lines = src.split("\n"); 16 | const line = lines[pos.line - 1]; // pos.line is 1-origin 17 | 18 | // Find nearest mappings 19 | let pBefore: MapPos | undefined = undefined; 20 | let pAfter: MapPos | undefined = undefined; 21 | for (let i = pos.column - 1; i > 0; i--) { 22 | const op = consumer.originalPositionFor({ line: pos.line, column: i }); 23 | if (op.line !== null) { 24 | pBefore = op as MapPos; 25 | break; 26 | } 27 | } 28 | for (let i = pos.column + 1; i <= line.length + 1; i++) { 29 | const op = consumer.originalPositionFor({ line: pos.line, column: i }); 30 | if (op.line !== null) { 31 | pAfter = op as MapPos; 32 | break; 33 | } 34 | } 35 | 36 | if (pBefore && pAfter) { 37 | return pos.column - pBefore.column < pAfter.column - pos.column 38 | ? pBefore 39 | : pAfter; 40 | } 41 | if (pBefore || pAfter) { 42 | return pBefore || pAfter; 43 | } 44 | 45 | return undefined; 46 | }; 47 | 48 | export const createPosTest = ( 49 | expect: jest.Expect, 50 | output: string, 51 | consumer: sourceMap.SourceMapConsumer 52 | ): any => ( 53 | line: number, 54 | column: number, 55 | expLine: number, 56 | expCol: number, 57 | source?: string 58 | ): void => { 59 | const op = getOriginalPos(output, { line, column }, consumer); 60 | if (op) { 61 | // t.deepEqual( 62 | // { line: op.line, column: op.column }, 63 | // { line: expLine, column: expCol } 64 | // ); 65 | // if (source) { 66 | // t.regex(op.source, new RegExp(source)); 67 | // } 68 | expect({ 69 | line: op.line, 70 | column: op.column 71 | }).toEqual({ line: expLine, column: expCol }); 72 | if (source) { 73 | expect(op.source).toMatch(new RegExp(source)); 74 | } 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /test/cli.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as cp from "child_process"; 3 | import * as fs from "fs"; 4 | import * as tmp from "tmp"; 5 | import p = require("pify"); 6 | 7 | const cmd = path.resolve(__dirname, "../bin/cli.js"); 8 | const exec = (...args: string[]) => { 9 | const proc = cp.spawnSync(cmd, args); 10 | return proc.stdout.toString(); 11 | }; 12 | 13 | const test01 = ` 14 | #pragma glslify: noise = require("glsl-noise/simplex/3d") 15 | precision mediump float; 16 | varying vec3 vpos; 17 | void main () { 18 | gl_FragColor = vec4(noise(vpos*25.0),1);", 19 | } 20 | `; 21 | 22 | test("CLI version", async (): Promise => { 23 | const output = exec("-v"); 24 | expect(output).toEqual(exec("--version")); 25 | }); 26 | 27 | test("CLI help", async (): Promise => { 28 | const output = exec("-h"); 29 | expect(output).toEqual(exec("--help")); 30 | expect(output).toMatch("Usage"); 31 | expect(output).toMatch("Example"); 32 | }); 33 | 34 | test("CLI read file and write to STDOUT", async (): Promise => { 35 | const input = path.resolve(__dirname, "fixtures/test01.frag"); 36 | expect(exec(input)).toMatch(/taylorInvSqrt/); 37 | }); 38 | 39 | test("CLI read file and write to file", async (): Promise => { 40 | const input = path.resolve(__dirname, "fixtures/test01.frag"); 41 | const dst = tmp.fileSync().name; 42 | exec(input, "-o", dst); 43 | 44 | const output = await p(fs.readFile)(dst, "utf8"); 45 | expect(output).toMatch(/taylorInvSqrt/); 46 | }); 47 | 48 | test("CLI read STDIN and write to STDOUT", async (): Promise => { 49 | const proc = cp.spawnSync(cmd, [], { input: test01 }); 50 | 51 | const output = proc.stdout.toString(); 52 | expect(output).toMatch(/taylorInvSqrt/); 53 | }); 54 | 55 | test("CLI read STDIN and write to file", async (): Promise => { 56 | const dst = tmp.fileSync().name; 57 | cp.spawnSync(cmd, ["-o", dst], { input: test01 }); 58 | 59 | const output = await p(fs.readFile)(dst, "utf8"); 60 | expect(output).toMatch(/taylorInvSqrt/); 61 | }); 62 | -------------------------------------------------------------------------------- /test/export.test.ts: -------------------------------------------------------------------------------- 1 | import * as glslify from ".."; 2 | import { file, compile } from ".."; 3 | 4 | test("ES modules import", async (): Promise => { 5 | expect(typeof glslify.compile).toEqual("function"); 6 | expect(typeof glslify.file).toEqual("function"); 7 | expect(glslify.compile).toEqual(compile); 8 | expect(glslify.file).toEqual(file); 9 | }); 10 | 11 | test("CommonJS require", async (): Promise => { 12 | const g = require(".."); 13 | expect(typeof g.compile).toEqual("function"); 14 | expect(typeof g.file).toEqual("function"); 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/hex.glsl: -------------------------------------------------------------------------------- 1 | void main() { 2 | gl_FragColor = #FFFF; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/import-1.glsl: -------------------------------------------------------------------------------- 1 | const float b = 2.0; 2 | -------------------------------------------------------------------------------- /test/fixtures/import-2.glsl: -------------------------------------------------------------------------------- 1 | const float d = 4.0; 2 | #pragma glslify: import(./import-3.glsl) 3 | const float e = 5.0; 4 | -------------------------------------------------------------------------------- /test/fixtures/import-3.glsl: -------------------------------------------------------------------------------- 1 | const float f = 6.0; 2 | const float g = 7.0; 3 | const float h = 8.0; 4 | -------------------------------------------------------------------------------- /test/fixtures/import-entry.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | const float a = 1.0; 3 | #pragma glslify: import(./import-1.glsl) 4 | const float c = 3.0; 5 | #pragma glslify: import('import-2.glsl') 6 | 7 | void main() { 8 | gl_FragColor = vec4(a, b, c, d); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/include-1.glsl: -------------------------------------------------------------------------------- 1 | const float b = 2.0; 2 | -------------------------------------------------------------------------------- /test/fixtures/include-2.glsl: -------------------------------------------------------------------------------- 1 | const float d = 4.0; 2 | #pragma glslify: import("./include-3.glsl") 3 | const float e = 5.0; 4 | -------------------------------------------------------------------------------- /test/fixtures/include-3.glsl: -------------------------------------------------------------------------------- 1 | const float f = 6.0; 2 | const float g = 7.0; 3 | const float h = 8.0; 4 | -------------------------------------------------------------------------------- /test/fixtures/include-entry.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | const float a = 1.0; 3 | #include "./include-1.glsl" 4 | const float c = 3.0; 5 | #include "include-2.glsl" 6 | 7 | void main() { 8 | gl_FragColor = vec4(a, b, c, d); 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/nest-conflict-1.glsl: -------------------------------------------------------------------------------- 1 | #pragma glslify: b = require(./nest-conflict-2.glsl) 2 | 3 | const float d; 4 | const vec2 c = vec2(2.0, b); 5 | 6 | #pragma glslify: export(c) 7 | -------------------------------------------------------------------------------- /test/fixtures/nest-conflict-2.glsl: -------------------------------------------------------------------------------- 1 | const float d = 1.0; 2 | 3 | #pragma glslify: export(d) 4 | -------------------------------------------------------------------------------- /test/fixtures/nest-conflict-entry.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | const float d; 4 | 5 | #pragma glslify: b = require(./nest-conflict-2.glsl) 6 | #pragma glslify: a = require(./nest-conflict-1.glsl) 7 | 8 | void main() { 9 | gl_FragColor = vec4(a, b, 1); 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/test01.frag: -------------------------------------------------------------------------------- 1 | #pragma glslify: noise = require("glsl-noise/simplex/3d") 2 | precision mediump float; 3 | varying vec3 vpos; 4 | void main () { 5 | gl_FragColor = vec4(noise(vpos*25.0),1); 6 | } 7 | -------------------------------------------------------------------------------- /test/import.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import gImport from "../src/glslify-import"; 4 | import { file } from "../src/glslify"; 5 | import * as convert from "convert-source-map"; 6 | import * as sourceMap from "source-map"; 7 | import { createPosTest } from "./_util"; 8 | 9 | test("nested imports", async (): Promise => { 10 | const filepath = path.resolve(__dirname, "fixtures/import-entry.glsl"); 11 | const input = fs.readFileSync(filepath, "utf8"); 12 | const output = await gImport(input, filepath); 13 | 14 | // Test sourcemaps 15 | const lastLine = output.split("\n").pop() as string; 16 | const sm = convert.fromComment(lastLine).toObject(); 17 | const consumer = await new sourceMap.SourceMapConsumer(sm); 18 | const hasPos = createPosTest(expect, output, consumer); 19 | 20 | hasPos(1, 1, 1, 1, "import-entry"); 21 | hasPos(2, 1, 2, 1, "import-entry"); 22 | 23 | hasPos(3, 1, 1, 1, "import-1"); 24 | hasPos(4, 1, 2, 1, "import-1"); // line for EOF 25 | 26 | hasPos(5, 1, 4, 1, "import-entry"); 27 | 28 | hasPos(6, 1, 1, 1, "import-2"); 29 | 30 | hasPos(7, 1, 1, 1, "import-3"); 31 | hasPos(8, 1, 2, 1, "import-3"); 32 | hasPos(9, 1, 3, 1, "import-3"); 33 | hasPos(10, 1, 4, 1, "import-3"); // line for EOF 34 | 35 | hasPos(11, 1, 3, 1, "import-2"); 36 | hasPos(12, 1, 4, 1, "import-2"); // line for EOF 37 | 38 | hasPos(14, 1, 7, 1, "import-entry"); 39 | 40 | consumer.destroy(); 41 | }); 42 | 43 | test("nested includes and imports", async (): Promise => { 44 | const filepath = path.resolve(__dirname, "fixtures/include-entry.glsl"); 45 | const input = fs.readFileSync(filepath, "utf8"); 46 | const output = await gImport(input, filepath); 47 | 48 | // Test sourcemaps 49 | const lastLine = output.split("\n").pop() as string; 50 | const sm = convert.fromComment(lastLine).toObject(); 51 | const consumer = await new sourceMap.SourceMapConsumer(sm); 52 | const hasPos = createPosTest(expect, output, consumer); 53 | 54 | hasPos(1, 1, 1, 1, "include-entry"); 55 | hasPos(2, 1, 2, 1, "include-entry"); 56 | 57 | hasPos(3, 1, 1, 1, "include-1"); 58 | hasPos(4, 1, 2, 1, "include-1"); // line for EOF 59 | 60 | hasPos(5, 1, 4, 1, "include-entry"); 61 | 62 | hasPos(6, 1, 1, 1, "include-2"); 63 | 64 | hasPos(7, 1, 1, 1, "include-3"); 65 | hasPos(8, 1, 2, 1, "include-3"); 66 | hasPos(9, 1, 3, 1, "include-3"); 67 | hasPos(10, 1, 4, 1, "include-3"); // line for EOF 68 | 69 | hasPos(11, 1, 3, 1, "include-2"); 70 | hasPos(12, 1, 4, 1, "include-2"); // line for EOF 71 | 72 | hasPos(14, 1, 7, 1, "include-entry"); 73 | 74 | consumer.destroy(); 75 | }); 76 | 77 | test("Bundling nested includes and imports", async (): Promise => { 78 | const filepath = path.resolve(__dirname, "fixtures/include-entry.glsl"); 79 | const output = await file(filepath); 80 | 81 | // Test sourcemaps 82 | const lastLine = output.split("\n").pop() as string; 83 | const sm = convert.fromComment(lastLine).toObject(); 84 | const consumer = await new sourceMap.SourceMapConsumer(sm); 85 | const hasPos = createPosTest(expect, output, consumer); 86 | 87 | hasPos(1, 1, 1, 1, "include-entry"); 88 | hasPos(2, 1, 2, 1, "include-entry"); 89 | 90 | hasPos(3, 1, 1, 1, "include-1"); 91 | hasPos(4, 1, 2, 1, "include-1"); // line for EOF 92 | 93 | hasPos(5, 1, 4, 1, "include-entry"); 94 | 95 | hasPos(6, 1, 1, 1, "include-2"); 96 | 97 | hasPos(7, 1, 1, 1, "include-3"); 98 | hasPos(8, 1, 2, 1, "include-3"); 99 | hasPos(9, 1, 3, 1, "include-3"); 100 | hasPos(10, 1, 4, 1, "include-3"); // line for EOF 101 | 102 | hasPos(11, 1, 3, 1, "include-2"); 103 | hasPos(12, 1, 4, 1, "include-2"); // line for EOF 104 | 105 | hasPos(14, 1, 7, 1, "include-entry"); 106 | 107 | consumer.destroy(); 108 | }); 109 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { file, compile } from "../src/glslify"; 3 | 4 | test("node string", async (): Promise => { 5 | var output = await compile( 6 | [ 7 | ' #pragma glslify: noise = require("glsl-noise/simplex/3d")', 8 | " precision mediump float;", 9 | " varying vec3 vpos;", 10 | " void main () {", 11 | " gl_FragColor = vec4(noise(vpos*25.0),1);", 12 | " }" 13 | ].join("\n") 14 | ); 15 | expect(output).toMatch(/taylorInvSqrt/); // contains parts of the file 16 | }); 17 | 18 | test("node file", async (): Promise => { 19 | var output = await file(path.resolve(__dirname, "fixtures/test01.frag")); 20 | expect(output).toMatch(/taylorInvSqrt/); // contains parts of the file 21 | }); 22 | -------------------------------------------------------------------------------- /test/source-map.test.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { file } from "../src/glslify"; 3 | import * as convert from "convert-source-map"; 4 | import * as sourceMap from "source-map"; 5 | import { createPosTest } from "./_util"; 6 | 7 | test("Import npm packages", async (): Promise => { 8 | const output = await file(path.resolve(__dirname, "fixtures/test01.frag")); 9 | 10 | // Test sourcemaps 11 | const lastLine = output.split("\n").pop() as string; 12 | expect(lastLine).toMatch( 13 | /\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,/ 14 | ); // contains sourceMaps of the file 15 | 16 | const sm = convert.fromComment(lastLine).toObject(); 17 | const consumer = await new sourceMap.SourceMapConsumer(sm); 18 | const hasPos = createPosTest(expect, output, consumer); 19 | 20 | // Line 12 21 | hasPos(12, 0, 12, 1); 22 | hasPos(12, 1, 12, 1); 23 | hasPos(12, 4, 12, 1); 24 | hasPos(12, 5, 12, 5); // TODO 25 | hasPos(12, 6, 12, 6); 26 | hasPos(12, 22, 12, 6); 27 | hasPos(12, 23, 12, 12); 28 | hasPos(12, 24, 12, 13); 29 | hasPos(12, 27, 12, 13); 30 | hasPos(12, 28, 12, 17); 31 | hasPos(12, 29, 12, 18); 32 | hasPos(12, 30, 12, 19); 33 | hasPos(12, 31, 12, 20); 34 | hasPos(12, 32, 12, 21); 35 | hasPos(12, 33, 12, 22); 36 | 37 | // Line 101 38 | hasPos(101, 0, 101, 33); 39 | hasPos(101, 1, 101, 33); 40 | hasPos(101, 35, 101, 33); 41 | hasPos(101, 36, 101, 36); 42 | hasPos(101, 58, 101, 58); 43 | hasPos(101, 59, 101, 59); 44 | hasPos(101, 60, 101, 60); 45 | 46 | // Line 109 47 | hasPos(109, 0, 5, 3); 48 | hasPos(109, 1, 5, 3); 49 | hasPos(109, 3, 5, 3); 50 | hasPos(109, 14, 5, 3); 51 | hasPos(109, 15, 5, 15); 52 | hasPos(109, 22, 5, 22); 53 | hasPos(109, 23, 5, 23); 54 | hasPos(109, 39, 5, 23); 55 | hasPos(109, 40, 5, 28); 56 | hasPos(109, 41, 5, 29); 57 | hasPos(109, 54, 5, 42); 58 | hasPos(109, 55, 5, 43); 59 | 60 | consumer.destroy(); 61 | }); 62 | 63 | test("nested imports", async (): Promise => { 64 | const output = await file( 65 | path.resolve(__dirname, "fixtures/nest-conflict-entry.glsl") 66 | ); 67 | 68 | // Test sourcemaps 69 | const lastLine = output.split("\n").pop() as string; 70 | const sm = convert.fromComment(lastLine).toObject(); 71 | const consumer = await new sourceMap.SourceMapConsumer(sm); 72 | const hasPos = createPosTest(expect, output, consumer); 73 | 74 | hasPos(1, 1, 1, 1, "nest-conflict-entry"); 75 | hasPos(3, 1, 3, 1, "nest-conflict-entry"); 76 | 77 | hasPos(5, 1, 1, 1, "nest-conflict-2"); 78 | 79 | hasPos(11, 1, 3, 1, "nest-conflict-1"); 80 | hasPos(12, 1, 4, 1, "nest-conflict-1"); 81 | 82 | hasPos(17, 1, 8, 1, "nest-conflict-entry"); 83 | hasPos(19, 1, 10, 1, "nest-conflict-entry"); 84 | 85 | consumer.destroy(); 86 | }); 87 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": ["es2015", "es2017", "dom"], 6 | "sourceMap": true, 7 | "outDir": "lib", 8 | "removeComments": true, 9 | "strict": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "moduleResolution": "node", 15 | "esModuleInterop": true 16 | }, 17 | "include": ["src", "typings"], 18 | "exclude": ["examples"] 19 | } 20 | -------------------------------------------------------------------------------- /typings/core.d.ts: -------------------------------------------------------------------------------- 1 | type DepsInfo = { 2 | id: number; 3 | deps: { [name: string]: number }; 4 | file: string; 5 | source: string; 6 | entry: boolean; 7 | 8 | // Property added by Bundle.preprocess 9 | parsed?: { 10 | tokens: Token[]; 11 | imports: DepImport[]; 12 | exports: string; 13 | }; 14 | }; 15 | 16 | type DepImport = { 17 | name: string; 18 | path: string; 19 | target: any; 20 | maps: any; 21 | index: number; 22 | }; 23 | 24 | type DepsHash = { 25 | [id: number]: DepsInfo; 26 | }; 27 | 28 | type DepperOptions = { 29 | cwd?: string; 30 | readFile?: Function; 31 | resolve?: Function; 32 | files?: any; 33 | }; 34 | type DepperCallback = (err: Error, result: DepsInfo[]) => void; 35 | type DepperTransformOptions = any; 36 | 37 | type Depper = { 38 | add(filename: string, callback: DepperCallback): void; 39 | inline(source: string, basedir: string, callback: DepperCallback): void; 40 | transform(transform: string, options: DepperTransformOptions): void; 41 | on(event: "file", callback: (filename: string) => void): void; 42 | }; 43 | 44 | type PostTransform = { 45 | name: string; 46 | opts: any; 47 | }; 48 | 49 | type Token = { 50 | type: 51 | | "block-comment" 52 | | "line-comment" 53 | | "preprocessor" 54 | | "operator" 55 | | "float" 56 | | "ident" 57 | | "builtin" 58 | | "eof" 59 | | "integer" 60 | | "whitespace" 61 | | "keyword"; 62 | data: string; 63 | position: number; 64 | line: number; 65 | column: number; 66 | 67 | source?: string; 68 | original?: { 69 | line: number; 70 | column: number; 71 | }; 72 | }; 73 | 74 | interface MapPos { 75 | line: number; 76 | column: number; 77 | name: string | null; 78 | source: string | null; 79 | } 80 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /typings/modules.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "glsl-tokenizer/string" { 4 | function tokenize(arg: string): Token[]; 5 | export = tokenize; 6 | } 7 | 8 | declare module "glsl-inject-defines" { 9 | type Defs = { 10 | [key: string]: number | string; 11 | }; 12 | function inject(src: string, defs: Defs): string; 13 | export = inject; 14 | } 15 | 16 | declare module "glsl-token-defines" { 17 | function defines(tokens: Token[]): Token[]; 18 | export = defines; 19 | } 20 | 21 | declare module "glsl-token-descope" { 22 | function descope(tokens: Token[], callback: any): Token[]; 23 | export = descope; 24 | } 25 | 26 | declare module "glsl-token-scope" { 27 | function scope(tokens: Token[]): Token[]; 28 | export = scope; 29 | } 30 | 31 | declare module "glsl-token-string" { 32 | function string(tokens: Token[]): string; 33 | export = string; 34 | } 35 | 36 | declare module "glsl-token-depth" { 37 | function depth(tokens: Token[]): Token[]; 38 | export = depth; 39 | } 40 | 41 | declare module "shallow-copy" { 42 | function copy(object: T): T; 43 | export = copy; 44 | } 45 | 46 | declare module "glslify-deps" { 47 | function deps(opts: DepperOptions): Depper; 48 | export = deps; 49 | } 50 | 51 | declare module "glsl-tokenizer" { 52 | function tokenize(src: string): Token[]; 53 | export = tokenize; 54 | } 55 | 56 | declare module "glsl-resolve" { 57 | function resolve(path: string, opts: { basedir: string }): Promise; 58 | export = resolve; 59 | } 60 | --------------------------------------------------------------------------------