diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1937215 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: https://editorconfig.org/ + +# top-most EditorConfig file +root = true + +[*.md] +trim_trailing_whitespace = false + +[*.js] +trim_trailing_whitespace = true + +# Unix-style newlines with a newline ending every file +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +insert_final_newline = true +max_line_length = 100 diff --git a/cacheWrap.js b/cacheWrap.js new file mode 100644 index 0000000..87e10f8 --- /dev/null +++ b/cacheWrap.js @@ -0,0 +1,29 @@ +var path = require('path') + +function cacheWrap(read, cache, async) { + // resolve all cached files such that they match + // all of the paths glslify handles, which are otherwise + // absolute + cache = Object.keys(cache).reduce(function(newCache, file) { + newCache[path.resolve(file)] = cache[file] + return newCache + }, {}) + + return function readFromCache(filename, done) { + if (!cache[filename]) { + if (async) { + return read(filename, done) + } + cache[filename] = read(filename) + } + + if (async) { + return process.nextTick(function() { + done(null, cache[filename]) + }) + } + return cache[filename] + } +} + +module.exports = cacheWrap; diff --git a/depper.js b/depper.js new file mode 100644 index 0000000..9a9c8a1 --- /dev/null +++ b/depper.js @@ -0,0 +1,293 @@ +var path = require('path') +var fs = require('graceful-fs') +var Emitter = require('events/') +var inherits = require('inherits') +var cacheWrap = require('./cacheWrap') +var nodeResolve = require('resolve') +var glslResolve = require('glsl-resolve') +var findup = require('@choojs/findup') + +var { + genInlineName, + getTransformsFromPkg, +} = require('./utils.js') + + +module.exports = Depper + +/** + * Creates a new instance of glslify-deps. Generally, you'll + * want to use one instance per bundle. + * + * note: this is an interface to be extended with a top class + * + * @class + * @param {String} cwd The root directory of your shader. Defaults to process.cwd() + */ +inherits(Depper, Emitter) +function Depper(opts, async) { + if (!(this instanceof Depper)) return new Depper(opts) + Emitter.call(this) + + opts = typeof opts === 'string' ? { cwd: opts } : opts + opts = opts || {} + + this._async = opts.async || async + this._deps = [] + this._cwd = opts.cwd || process.cwd() + this._cache = {} + this._i = 0 + this._transforms = [] + this._trCache = {} + this._fileCache = opts.files || {} + + this._globalTransforms = [] + + this._readFile = cacheWrap(opts.readFile || createDefaultRead(this._async), this._fileCache, this._async) + this.resolve = opts.resolve || (this._async ? glslResolve : glslResolve.sync) + + this._inlineSource = '' + this._inlineName = genInlineName() + + if (typeof this._cwd !== 'string') { + throw new Error('glslify-deps: cwd must be a string path') + } +} + +Depper.prototype.inline = function(source, basedir, done) { + var inlineFile = path.resolve(basedir || this._cwd, this._inlineName) + + this._inlineSource = source + + if (this._async) { + this.add(inlineFile, function(err, tree) { + done && done(err, !err && tree) + }) + } else { + return this.add(inlineFile) + } +} + +/** + * Internal method to add dependencies + * @param {string} filename + */ +Depper.prototype._addDep = function(filename) { + var dep = { + id: this._i++ + , deps: {} + , file: filename + , source: null + , entry: this._i === 1 + } + + this._deps.push(dep) + + return dep; +} + + +/** + * Adds a transform to use on your local dependencies. + * Note that this should be used before calling `add`. + * + * Transforms are handled using a different API to browserify, e.g.: + * + * ``` js + * module.exports = function transform(filename, src, opts, done) { + * done(null, src.toUpperCase()) + * } + * ``` + * + * Where `filename` is the absolute file path, `src` is the shader source + * as a string, `opts` is an options object for configuration, and `done` + * is a callback which takes the transformed shader source. + * + * @param {String|Function} transform + * @param {Object} opts + */ +Depper.prototype.transform = function(transform, opts) { + var name = typeof transform === 'string' ? transform : null + var list = opts && opts.global + ? this._globalTransforms + : this._transforms + + // post transforms are ignored by glslify-deps, to be handled + // by glslify after the file has been bundled. + if (opts && opts.post) return this + + transform = this.resolveTransform(transform) + list.push({ tr: transform, opts: opts, name: name }) + + return this +} + +/** + * Resolves a transform. + * + * Functions are retained as-is. + * Strings are resolved using node's `require` resolution algorithm, + * and then required directly. + * + * @param {String|Function} transform + */ +Depper.prototype.resolveTransform = function(transform) { + if (typeof transform === 'string') { + transform = nodeResolve.sync(transform, { + basedir: this._cwd + }) + if (this._async) { + transform = require(transform) + } else { + var m = require(transform) + if (!m || typeof m.sync !== 'function') { + throw new Error('transform ' + transform + ' does not provide a' + + ' synchronous interface') + } + transform = m.sync + } + } + return transform +} + +/** + * Applies a transform to a string. + * + * Note that transforms here are passed in differently to other methods: + * - `tr.tr` should point to the transform function. + * - `tr.opts` should contain the options for the transform, if applicable. + * + * @param {String} filename The absolute path of the file you're transforming. + * @param {String} src The shader source you'd like to transform. + * @param {Array} transforms The transforms you'd like to apply. + * @param {(err: Error, result: string) => any} [done] Applies when async true + */ +Depper.prototype.applyTransforms = function(filename, src, transforms, done) { + if (this._async) { + var i = 0 + + next(null, src) + function next(err, updated) { + if (err) return done(err) + if (i >= transforms.length) return done(null, updated) + + var tr = transforms[i++] + var opts = tr.opts + + if (!opts || typeof opts !== 'object') opts = {} + tr.tr(filename, updated+'', tr.opts, next) + } + } else { + transforms.forEach(function (tr) { + var opts = tr.opts + if (!opts || typeof opts !== 'object') opts = {} + src = tr.tr(filename, src+'', tr.opts) + }) + return src + } +} + +/** + * Determines which transforms to use for a particular file. + * The rules here are the same you see in browserify: + * + * - your shader files will have your specified transforms applied to them + * - shader files in node_modules do not get local transforms + * - all files will apply transforms specified in `glslify.transform` in your + * `package.json` file, albeit after any transforms you specified using + * `depper.transform`. + * + * @param {String} filename The absolute path of the file in question. + * @param {(err: Error, transforms: any) => any} [done] Applies when async true + */ +Depper.prototype.getTransformsForFile = function(filename, done) { + var self = this + var entry = this._deps[0] + + if (!entry) return done(new Error( + 'getTransformsForFile may only be called after adding your entry file' + )) + + var entryDir = path.dirname(path.resolve(entry.file)) + var fileDir = path.dirname(path.resolve(filename)) + var relative = path.relative(entryDir, fileDir).split(path.sep) + var node_modules = relative.indexOf('node_modules') !== -1 + var trLocal = node_modules ? [] : this._transforms + var trCache = this._trCache + var pkgName = 'package.json' + + if (trCache[fileDir]) { + if (this._async) { + return done(null, trCache[fileDir]) + } else { + return trCache[fileDir] + } + } + + function register(transforms) { + trCache[fileDir] = trLocal + .concat(transforms.map(function(tr) { + tr.tr = self.resolveTransform(tr.tr) + return tr + })) + .concat(self._globalTransforms); + var result = trCache[fileDir] + if (self._async) { + done(null, result) + } else { + return result + } + } + + if (this._async) { + findup(fileDir, pkgName, function(err, found) { + var notFound = err && err.message === 'not found' + if (notFound) return register([]) + if (err) return done(err) + + var pkg = path.join(found, pkgName) + + self.readFile(pkg, function(err, pkgJson) { + if (err) return done(err) + var transforms; + try { + transforms = getTransformsFromPkg(pkgJson) + } catch(e) { return done(e) } + + register(transforms) + }) + }) + } else { + try { var found = findup.sync(fileDir, pkgName) } + catch (err) { + var notFound = err.message === 'not found' + if (notFound) return register([]) + else throw err + } + + var pkg = path.join(found, pkgName) + + return register(getTransformsFromPkg(self.readFile(pkg))) + } +} + +Depper.prototype.readFile = function(filename, done) { + if (path.basename(filename) !== this._inlineName) + return this._readFile(filename, done) + + if(this._async) { + return done(null, this._inlineSource) + } + return this._inlineSource +} + +function createDefaultRead(async) { + if (async) { + return function defaultRead(src, done) { + fs.readFile(src, 'utf8', done) + } + } + return function defaultRead(src) { + return fs.readFileSync(src, 'utf8') + } +} diff --git a/index.js b/index.js index a0ecbb4..228c935 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,14 @@ -var tokenize = require('glsl-tokenizer/string') -var findup = require('@choojs/findup') -var fs = require('graceful-fs') var map = require('map-limit') var inherits = require('inherits') -var Emitter = require('events/') var path = require('path') +var Depper = require('./depper') -var glslResolve = require('glsl-resolve') -var nodeResolve = require('resolve') +var { + getImportName, + extractPreprocessors +} = require('./utils'); -var inlineName = '__INLINE__' + Math.random() -var inlineSource = '' - -module.exports = Depper +module.exports = DepperAsync /** * Creates a new instance of glslify-deps. Generally, you'll @@ -21,75 +17,10 @@ module.exports = Depper * @class * @param {String} cwd The root directory of your shader. Defaults to process.cwd() */ -inherits(Depper, Emitter) -function Depper(opts) { - if (!(this instanceof Depper)) return new Depper(opts) - Emitter.call(this) - - opts = typeof opts === 'string' ? { cwd: opts } : opts - opts = opts || {} - - this._deps = [] - this._cwd = opts.cwd || process.cwd() - this._cache = {} - this._i = 0 - this._transforms = [] - this._trCache = {} - this._fileCache = opts.files || {} - - this._globalTransforms = [] - - this._readFile = cacheWrap(opts.readFile || defaultRead, this._fileCache) - this.resolve = opts.resolve || glslResolve - - if (typeof this._cwd !== 'string') { - throw new Error('glslify-deps: cwd must be a string path') - } -} - -Depper.prototype.inline = function(source, basedir, done) { - var inlineFile = path.resolve(basedir || process.cwd(), inlineName) - - inlineSource = source - - this.add(inlineFile, function(err, tree) { - done && done(err, !err && tree) - }) -} - -/** - * Adds a transform to use on your local dependencies. - * Note that this should be used before calling `add`. - * - * Transforms are handled using a different API to browserify, e.g.: - * - * ``` js - * module.exports = function transform(filename, src, opts, done) { - * done(null, src.toUpperCase()) - * } - * ``` - * - * Where `filename` is the absolute file path, `src` is the shader source - * as a string, `opts` is an options object for configuration, and `done` - * is a callback which takes the transformed shader source. - * - * @param {String|Function} transform - * @param {Object} opts - */ -Depper.prototype.transform = function(transform, opts) { - var name = typeof transform === 'string' ? transform : null - var list = opts && opts.global - ? this._globalTransforms - : this._transforms - - // post transforms are ignored by glslify-deps, to be handled - // by glslify after the file has been bundled. - if (opts && opts.post) return this - - transform = this.resolveTransform(transform) - list.push({ tr: transform, opts: opts, name: name }) - - return this +inherits(DepperAsync, Depper) +function DepperAsync(opts) { + if (!(this instanceof DepperAsync)) return new DepperAsync(opts) + Depper.call(this, opts, true); } /** @@ -99,29 +30,19 @@ Depper.prototype.transform = function(transform, opts) { * modules. * * @param {String} filename The absolute path of this file. - * @param {String} src The shader source for this file. * @param {Function} done(err, deps) * * The `done` callback will be called when the entire graph has been * resolved, and will include an array of dependencies discovered * so far as its second argument. */ -Depper.prototype.add = function(filename, done) { +DepperAsync.prototype.add = function(filename, done) { var basedir = path.dirname(filename = path.resolve(filename)) - var cache = this._cache var self = this var exports = [] var imports = [] - var dep = { - id: this._i++ - , deps: {} - , file: filename - , source: null - , entry: this._i === 1 - } - - this._deps.push(dep) + var dep = this._addDep(filename) this.readFile(filename, function(err, src) { if (err) return done(err) @@ -133,8 +54,11 @@ Depper.prototype.add = function(filename, done) { if (err) return done(err) dep.source = src - extractPreprocessors() - resolveImports(function(err) { + extractPreprocessors(dep.source, imports, exports) + self._resolveImports(imports, { + deps: dep.deps, + basedir: basedir + }, function(err) { setTimeout(function() { done && done(err, !err && self._deps) }) @@ -144,219 +68,39 @@ Depper.prototype.add = function(filename, done) { }) return dep - - function extractPreprocessors() { - var tokens = tokenize(dep.source) - - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i] - if (token.type !== 'preprocessor') continue - - var data = token.data - if (!glslifyPreprocessor(data)) continue - - var exp = glslifyExport(data) - var imp = glslifyImport(data) - if (exp) exports.push(exp[1]) - if (imp) imports.push(imp[2]) - } - } - - function resolveImports(resolved) { - map(imports, 10, function(imp, next) { - var importName = imp.split(/\s*,\s*/).shift() - - importName = importName.trim() - importName = importName.replace(/^'|'$/g, '') - importName = importName.replace(/^"|"$/g, '') - - self.resolve(importName, { basedir: basedir }, function(err, resolved) { - if (err) return next(err) - - if (cache[resolved]) { - dep.deps[importName] = cache[resolved].id - return next() - } - - cache[resolved] = self.add(resolved, function(err) { - if (err) return next(err) - dep.deps[importName] = cache[resolved].id - next() - }) - }) - }, resolved) - } -} - -Depper.prototype.readFile = function(filename, done) { - if (path.basename(filename) !== inlineName) - return this._readFile(filename, done) - - return done(null, inlineSource) } /** - * Determines which transforms to use for a particular file. - * The rules here are the same you see in browserify: + * Internal async method to retrieve dependencies + * resolving imports using the internal cache * - * - your shader files will have your specified transforms applied to them - * - shader files in node_modules do not get local transforms - * - all files will apply transforms specified in `glslify.transform` in your - * `package.json` file, albeit after any transforms you specified using - * `depper.transform`. - * - * @param {String} filename The absolute path of the file in question. + * @param {string[]} imports + * @param {object} opts extends options for https://www.npmjs.com/package/resolve + * @param {object} opts.deps existing dependencies + * @param {(err: Error)} done + * @return {object} resolved dependencies */ -Depper.prototype.getTransformsForFile = function(filename, done) { - var self = this - var entry = this._deps[0] - - if (!entry) return done(new Error( - 'getTransformsForFile may only be called after adding your entry file' - )) - - var entryDir = path.dirname(path.resolve(entry.file)) - var fileDir = path.dirname(path.resolve(filename)) - var relative = path.relative(entryDir, fileDir).split(path.sep) - var node_modules = relative.indexOf('node_modules') !== -1 - var trLocal = node_modules ? [] : this._transforms - var trCache = this._trCache +DepperAsync.prototype._resolveImports = function(imports, opts, done) { + var self = this + var deps = opts && opts.deps || {} + map(imports, 10, function(imp, next) { + var importName = getImportName(imp) - if (trCache[fileDir]) { - return done(null, trCache[fileDir]) - } - - findup(fileDir, 'package.json', function(err, found) { - var notFound = err && err.message === 'not found' - if (notFound) return register([]) - if (err) return done(err) + self.resolve(importName, opts, function(err, resolved) { + if (err) return next(err) - var pkg = path.join(found, 'package.json') + if (self._cache[resolved]) { + deps[importName] = self._cache[resolved].id + return next() + } - self.readFile(pkg, function(err, pkgjson) { - if (err) return done(err) - - try { - pkgjson = JSON.parse(pkgjson) - } catch(e) { return done(e) } - - var transforms = ( - pkgjson['glslify'] - && pkgjson['glslify']['transform'] - || [] - ) - - transforms = transforms.map(function(key) { - var transform = Array.isArray(key) - ? key - : [key, {}] - - var key = transform[0] - var opt = transform[1] - - if (opt) { - delete opt.global - delete opt.post - } - - return { tr: key, opts: opt, name: key } - }).map(function(tr) { - tr.tr = self.resolveTransform(tr.tr) - return tr + self._cache[resolved] = self.add(resolved, function(err) { + if (err) return next(err) + deps[importName] = self._cache[resolved].id + next() }) - - register(transforms) }) - }) - - function register(transforms) { - done(null, trCache[fileDir] = trLocal - .concat(transforms) - .concat(self._globalTransforms)) - } -} - -/** - * Resolves a transform. - * - * Functions are retained as-is. - * Strings are resolved using node's `require` resolution algorithm, - * and then required directly. - * - * @param {String|Function} transform - */ -Depper.prototype.resolveTransform = function(transform) { - if (typeof transform === 'string') { - transform = nodeResolve.sync(transform, { - basedir: this._cwd - }) - - transform = require(transform) - } - - return transform -} + }, done) -/** - * Applies a transform to a string. - * - * Note that transforms here are passed in differently to other methods: - * - `tr.tr` should point to the transform function. - * - `tr.opts` should contain the options for the transform, if applicable. - * - * @param {String} filename The absolute path of the file you're transforming. - * @param {String} src The shader source you'd like to transform. - * @param {Array} transforms The transforms you'd like to apply. - * @param {Function} done(err, transformed) - */ -Depper.prototype.applyTransforms = function(filename, src, transforms, done) { - var i = 0 - - next(null, src) - function next(err, updated) { - if (err) return done(err) - if (i >= transforms.length) return done(null, updated) - - var tr = transforms[i++] - var opts = tr.opts - - if (!opts || typeof opts !== 'object') opts = {} - tr.tr(filename, updated+'', tr.opts, next) - } -} - -function glslifyPreprocessor(data) { - return /#pragma glslify:/.test(data) -} - -function glslifyExport(data) { - return /#pragma glslify:\s*export\(([^\)]+)\)/.exec(data) -} - -function glslifyImport(data) { - return /#pragma glslify:\s*([^=\s]+)\s*=\s*require\(([^\)]+)\)/.exec(data) -} - -function defaultRead(src, done) { - fs.readFile(src, 'utf8', done) -} - -function cacheWrap(read, cache) { - // resolve all cached files such that they match - // all of the paths glslify handles, which are otherwise - // absolute - cache = Object.keys(cache).reduce(function(newCache, file) { - newCache[path.resolve(file)] = cache[file] - return newCache - }, {}) - - return function readFromCache(filename, done) { - if (!cache[filename]) { - return read(filename, done) - } - - process.nextTick(function() { - done(null, cache[filename]) - }) - } + return deps } diff --git a/sync.js b/sync.js index 54fe512..241a6c7 100644 --- a/sync.js +++ b/sync.js @@ -1,18 +1,13 @@ -var tokenize = require('glsl-tokenizer/string') -var findup = require('@choojs/findup').sync -var fs = require('graceful-fs') -var map = require('map-limit') var inherits = require('inherits') -var Emitter = require('events/') var path = require('path') +var Depper = require('./depper') -var glslResolve = require('glsl-resolve').sync -var nodeResolve = require('resolve').sync +var { + getImportName, + extractPreprocessors +} = require('./utils'); -var inlineName = '__INLINE__' + Math.random() -var inlineSource = '' - -module.exports = Depper +module.exports = DepperSync /** * Creates a new instance of glslify-deps. Generally, you'll @@ -21,74 +16,10 @@ module.exports = Depper * @class * @param {String} cwd The root directory of your shader. Defaults to process.cwd() */ -inherits(Depper, Emitter) -function Depper(opts) { - if (!(this instanceof Depper)) return new Depper(opts) - Emitter.call(this) - - opts = typeof opts === 'string' ? { cwd: opts } : opts - opts = opts || {} - - this._deps = [] - this._cwd = opts.cwd || process.cwd() - this._cache = {} - this._i = 0 - this._transforms = [] - this._trCache = {} - this._fileCache = opts.files || {} - - this._globalTransforms = [] - - this._readFile = cacheWrap(opts.readFileSync || defaultRead, this._fileCache) - this.resolve = opts.resolve || glslResolve - - if (typeof this._cwd !== 'string') { - throw new Error('glslify-deps: cwd must be a string path') - } -} - -Depper.prototype.inline = function(source, basedir) { - var inlineFile = path.resolve(basedir || process.cwd(), inlineName) - - inlineSource = source - - return this.add(inlineFile) -} - -/** - * Adds a transform to use on your local dependencies. - * Note that this should be used before calling `add`. - * - * Transforms are handled using a different API to browserify, e.g.: - * - * ``` js - * exports.sync = function transform(filename, src, opts) { - * return src.toUpperCase() - * } - * ``` - * - * This is also different from the async transform API. - * - * Where `filename` is the absolute file path, `src` is the shader source - * as a string, `opts` is an options object for configuration. - * - * @param {String|Function} transform - * @param {Object} opts - */ -Depper.prototype.transform = function(transform, opts) { - var name = typeof transform === 'string' ? transform : null - var list = opts && opts.global - ? this._globalTransforms - : this._transforms - - // post transforms are ignored by glslify-deps, to be handled - // by glslify after the file has been bundled. - if (opts && opts.post) return this - - transform = this.resolveTransform(transform) - list.push({ tr: transform, opts: opts, name: name }) - - return this +inherits(DepperSync, Depper) +function DepperSync(opts) { + if (!(this instanceof DepperSync)) return new DepperSync(opts) + Depper.call(this, opts) } /** @@ -98,230 +29,54 @@ Depper.prototype.transform = function(transform, opts) { * modules. * * @param {String} filename The absolute path of this file. - * @param {String} src The shader source for this file. * * Returns an array of dependencies discovered so far as its second argument. */ -Depper.prototype.add = function(filename) { +DepperSync.prototype.add = function(filename) { var basedir = path.dirname(filename = path.resolve(filename)) - var cache = this._cache - var self = this var exports = [] var imports = [] - var dep = { - id: this._i++ - , deps: {} - , file: filename - , source: null - , entry: this._i === 1 - } - - this._deps.push(dep) + var dep = this._addDep(filename) var src = this.readFile(filename) - var trs = self.getTransformsForFile(filename) - self.emit('file', filename) - src = self.applyTransforms(filename, src, trs) + var trs = this.getTransformsForFile(filename) + this.emit('file', filename) + src = this.applyTransforms(filename, src, trs) dep.source = src - extractPreprocessors() - - resolveImports() - return self._deps - - function extractPreprocessors() { - var tokens = tokenize(dep.source) - - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i] - if (token.type !== 'preprocessor') continue - - var data = token.data - if (!glslifyPreprocessor(data)) continue - - var exp = glslifyExport(data) - var imp = glslifyImport(data) - if (exp) exports.push(exp[1]) - if (imp) imports.push(imp[2]) - } - } - - function resolveImports(resolved) { - imports.forEach(function (imp) { - var importName = imp.split(/\s*,\s*/).shift() - - importName = importName.trim() - importName = importName.replace(/^'|'$/g, '') - importName = importName.replace(/^"|"$/g, '') - - var resolved = self.resolve(importName, { basedir: basedir }) - if (cache[resolved]) { - dep.deps[importName] = cache[resolved].id - } - var i = self._i - cache[resolved] = self.add(resolved)[i] - dep.deps[importName] = cache[resolved].id - }) - } -} + extractPreprocessors(dep.source, imports, exports) -Depper.prototype.readFile = function(filename) { - if (path.basename(filename) !== inlineName) - return this._readFile(filename) - - return inlineSource -} - -/** - * Determines which transforms to use for a particular file. - * The rules here are the same you see in browserify: - * - * - your shader files will have your specified transforms applied to them - * - shader files in node_modules do not get local transforms - * - all files will apply transforms specified in `glslify.transform` in your - * `package.json` file, albeit after any transforms you specified using - * `depper.transform`. - * - * @param {String} filename The absolute path of the file in question. - */ -Depper.prototype.getTransformsForFile = function(filename) { - var self = this - var entry = this._deps[0] - - if (!entry) throw new Error( - 'getTransformsForFile may only be called after adding your entry file' - ) - - var entryDir = path.dirname(path.resolve(entry.file)) - var fileDir = path.dirname(path.resolve(filename)) - var relative = path.relative(entryDir, fileDir).split(path.sep) - var node_modules = relative.indexOf('node_modules') !== -1 - var trLocal = node_modules ? [] : this._transforms - var trCache = this._trCache - - if (trCache[fileDir]) { - return trCache[fileDir] - } - - try { var found = findup(fileDir, 'package.json') } - catch (err) { - var notFound = err.message === 'not found' - if (notFound) return register([]) - else throw err - } - - var pkg = path.join(found, 'package.json') - var pkgjson = JSON.parse(self.readFile(pkg)) - - var transforms = ( - pkgjson['glslify'] - && pkgjson['glslify']['transform'] - || [] - ) - - transforms = transforms.map(function(key) { - var transform = Array.isArray(key) - ? key - : [key, {}] - - var key = transform[0] - var opt = transform[1] - - if (opt) { - delete opt.global - delete opt.post - } - - return { tr: key, opts: opt, name: key } - }).map(function(tr) { - tr.tr = self.resolveTransform(tr.tr) - return tr + this._resolveImports(imports, { + basedir: basedir, + deps: dep.deps }) - return register(transforms) - - function register(transforms) { - return trCache[fileDir] = trLocal - .concat(transforms) - .concat(self._globalTransforms) - } + return this._deps } /** - * Resolves a transform. - * - * Functions are retained as-is. - * Strings are resolved using node's `require` resolution algorithm, - * and then required directly. + * Internal sync method to retrieve dependencies + * resolving imports using the internal cache * - * @param {String|Function} transform + * @param {string[]} imports + * @param {object} opts extends options for https://www.npmjs.com/package/resolve + * @param {object} opts.deps existing dependencies + * @return {object} resolved dependencies */ -Depper.prototype.resolveTransform = function(transform) { - if (typeof transform === 'string') { - transform = nodeResolve(transform, { - basedir: this._cwd - }) +DepperSync.prototype._resolveImports = function(imports, opts) { + var self = this + var deps = opts && opts.deps || {} - var m = require(transform) - if (!m || typeof m.sync !== 'function') { - throw new Error('transform ' + transform + ' does not provide a' - + ' synchronous interface') - } - transform = m.sync - } - return transform -} + imports.forEach(function (imp) { + var importName = getImportName(imp) -/** - * Applies a transform to a string. - * - * Note that transforms here are passed in differently to other methods: - * - `tr.tr` should point to the transform function. - * - `tr.opts` should contain the options for the transform, if applicable. - * - * @param {String} filename The absolute path of the file you're transforming. - * @param {String} src The shader source you'd like to transform. - * @param {Array} transforms The transforms you'd like to apply. - * - * Returns the transformed source string. - */ -Depper.prototype.applyTransforms = function(filename, src, transforms) { - transforms.forEach(function (tr) { - var opts = tr.opts - if (!opts || typeof opts !== 'object') opts = {} - src = tr.tr(filename, src+'', tr.opts) + var resolved = self.resolve(importName, opts) + if (self._cache[resolved]) { + deps[importName] = self._cache[resolved].id + } + var i = self._i + self._cache[resolved] = self.add(resolved)[i] + deps[importName] = self._cache[resolved].id }) - return src -} - -function glslifyPreprocessor(data) { - return /#pragma glslify:/.test(data) -} -function glslifyExport(data) { - return /#pragma glslify:\s*export\(([^\)]+)\)/.exec(data) -} - -function glslifyImport(data) { - return /#pragma glslify:\s*([^=\s]+)\s*=\s*require\(([^\)]+)\)/.exec(data) -} - -function defaultRead(src) { - return fs.readFileSync(src, 'utf8') -} - -function cacheWrap(read, cache) { - // resolve all cached files such that they match - // all of the paths glslify handles, which are otherwise - // absolute - cache = Object.keys(cache).reduce(function(newCache, file) { - newCache[path.resolve(file)] = cache[file] - return newCache - }, {}) - - return function readFromCache(filename) { - if (!cache[filename]) { - cache[filename] = read(filename) - } - return cache[filename] - } + return deps } diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..a0ac973 --- /dev/null +++ b/utils.js @@ -0,0 +1,92 @@ +var tokenize = require('glsl-tokenizer/string') + +function glslifyPreprocessor(data) { + return /#pragma glslify:/.test(data) +} + +function glslifyExport(data) { + return /#pragma glslify:\s*export\(([^\)]+)\)/.exec(data) +} + +function glslifyImport(data) { + return /#pragma glslify:\s*([^=\s]+)\s*=\s*require\(([^\)]+)\)/.exec(data) +} + +function genInlineName() { + return '__INLINE__' + Math.random() +} + + +/** + * Gets glslify transform from given package.json + * + * @param {object|string} pkgJson package.json filename path or json + * @returns {({tr: string, name: string, opts: object})[]} + */ +function getTransformsFromPkg(pkgJson) { + if (typeof pkgJson === 'string') { + pkgJson = JSON.parse(pkgJson); + } + + var transforms = ( + pkgJson['glslify'] + && pkgJson['glslify']['transform'] + || [] + ) + + return transforms.map(function(key) { + var transform = Array.isArray(key) + ? key + : [key, {}] + + var key = transform[0] + var opt = transform[1] + + if (opt) { + delete opt.global + delete opt.post + } + + return { tr: key, opts: opt, name: key } + }); +} + +/** + * Extracts preprocessors copying the imports and exports + * into respective parameters + * @param {string} source + * @param {string[]} imports + * @param {string[]} exports + */ +function extractPreprocessors(source, imports, exports) { + var tokens = tokenize(source) + + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i] + if (token.type !== 'preprocessor') continue + + var data = token.data + if (!glslifyPreprocessor(data)) continue + + var exp = glslifyExport(data) + var imp = glslifyImport(data) + if (exp) exports.push(exp[1]) + if (imp) imports.push(imp[2]) + } +} + +function getImportName(imp) { + return imp + .split(/\s*,\s*/) + .shift() + .trim() + .replace(/^'|'$/g, '') + .replace(/^"|"$/g, '') +} + +module.exports = { + getTransformsFromPkg, + getImportName, + extractPreprocessors, + genInlineName +}