diff options
Diffstat (limited to 'node_modules/stylus-loader/lib/pathcache.js')
| -rw-r--r-- | node_modules/stylus-loader/lib/pathcache.js | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/node_modules/stylus-loader/lib/pathcache.js b/node_modules/stylus-loader/lib/pathcache.js new file mode 100644 index 00000000..5437cef9 --- /dev/null +++ b/node_modules/stylus-loader/lib/pathcache.js @@ -0,0 +1,304 @@ +var path = require('path'); +var fs = require('fs'); + +var Evaluator = require('stylus/lib/visitor/evaluator'); +var loaderUtils = require('loader-utils'); +var nodes = require('stylus/lib/nodes'); +var utils = require('stylus/lib/utils'); +var when = require('when'); +var whenNodefn = require('when/node/function'); + +var listImports = require('./listimports'); + +module.exports = PathCache; + +var readFile = whenNodefn.lift(fs.readFile); + +// A cache of import paths of a stylus file resolved to their location on disk +// before the stylus file is rendered. With a special evaluator this lets us +// webpack's resolver. +function PathCache(contexts, sources, imports) { + this.contexts = contexts; + this.sources = sources; + this.imports = imports; + + // Non relative paths are simpler and looked up in this as a fallback + // to this.context. + this.simpleContext = {}; + for (var dirname in this.contexts) { + for (var path in this.contexts[dirname]) { + this.simpleContext[path] = this.contexts[dirname][path]; + } + } +} + +// Return a promise for a PathCache. +PathCache.create = function(contexts, sources, imports) { + return when(new PathCache(contexts, sources, imports)); +}; +PathCache.createFromFile = resolveFileDeep; + +// Create a list of ways to resolve paths. +PathCache.resolvers = resolvers; + +PathCache.resolvers.reduce = reduceResolvers; + +// Lookup the path in this cache. +PathCache.prototype.find = function(path, dirname) { + if (this.contexts[dirname] && this.contexts[dirname][path]) { + return this.contexts[dirname][path].path; + } else if (this.simpleContext[path]) { + return this.simpleContext[path].path; + } else if (/.styl$/.test(path)) { + // A user can specify @import 'something.styl' but if they specify + // @import 'something' stylus adds .styl, we drop that here to see if we + // looked for it without .styl. + return this.find(path.replace(/.styl$/, ''), dirname); + } else { + return undefined; + } +}; + +// Return if the path in this cache is an index file. +PathCache.prototype.isIndex = function(path, dirname) { + if (this.contexts[dirname] && this.contexts[dirname][path]) { + return this.contexts[dirname][path].index; + } else { + return undefined; + } +}; + +// Return an array of all imports the original file depends on. +PathCache.prototype.allDeps = function() { + var deps = []; + for (var dirname in this.contexts) { + for (var path in this.contexts[dirname]) { + if (this.contexts[dirname][path]) { + deps = deps.concat(this.contexts[dirname][path].path); + } + } + } + return deps; +}; + +// Create an array of ways to resolve a path. +// +// The resolved paths may be a path or an object specifying path and index +// members. The index member is used later by stylus, we store it at this point. +function resolvers(options, webpackResolver) { + var evaluator = new Evaluator(nodes.null, options); + var whenWebpackResolver = whenNodefn.lift(webpackResolver); + + // Stylus's normal resolver for single files. + var stylusFile = function(context, path) { + // Stylus adds .styl to paths for normal "paths" lookup if it isn't there. + if (!/.styl$/.test(path)) { + path += '.styl'; + } + + var paths = options.paths.concat(context); + var found = utils.find(path, paths, options.filename) + if (found) { + return normalizePaths(found); + } + }; + + // Stylus's normal resolver for node_modules packages. Cannot locate paths + // inside a package. + var stylusIndex = function(context, path) { + // Stylus calls the argument name. If it exists it should match the name + // of a module in node_modules. + if (!path) { + return null; + } + + var paths = options.paths.concat(context); + var found = utils.lookupIndex(path, paths, options.filename); + if (found) { + return {path: normalizePaths(found), index: true}; + } + }; + + // Fallback to resolving with webpack's configured resovler. + var webpackResolve = function(context, path) { + // Follow the webpack stylesheet idiom of '~path' meaning a path in a + // modules folder and a unprefixed 'path' meaning a relative path like + // './path'. + path = loaderUtils.urlToRequest(path, options.root); + // First try with a '.styl' extension. + return whenWebpackResolver(context, path + '.styl') + // If the user adds ".styl" to resolve.extensions, webpack can find + // index files like stylus but it uses all of webpack's configuration, + // by default for example the module could be web_modules. + .catch(function() { return whenWebpackResolver(context, path); }) + .catch(function() { return null; }) + .then(function(result) { + return Array.isArray(result) && result[1] && result[1].path || result + }); + }; + + if (options.preferPathResolver === 'webpack') { + return [ + webpackResolve, + stylusFile, + stylusIndex + ]; + } + else { + return [ + stylusFile, + stylusIndex, + webpackResolve + ]; + } +} + +function reduceResolvers(resolvers, context, path) { + return when + .reduce(resolvers, function(result, resolver) { + return result ? result : resolver(context, path); + }, undefined); +} + +// Run resolvers on one path and return an object with the found path under a +// key of the original path. +// +// Example: +// resolving the path +// 'a/file' +// returns an object +// {'a/file': {path: ['node_modules/a/file'], index: true}} +function resolveOne(resolvers, context, path) { + return reduceResolvers(resolvers, context, path) + .then(function(result) { + result = typeof result === 'string' ? [result] : result; + result = Array.isArray(result) ? {path: result, index: false} : result; + var map = {}; + map[path] = result; + return map; + }); +} + +// Run the resolvers on an array of paths and return an object like resolveOne. +function resolveMany(resolvers, context, paths) { + return when + .map(paths, resolveOne.bind(null, resolvers, context)) + .then(function(maps) { + return maps.reduce(function(map, resolvedPaths) { + Object.keys(resolvedPaths).forEach(function(path) { + map[path] = resolvedPaths[path]; + }); + return map; + }, {}); + }); +} + +// Load a file at fullPath, resolve all of it's imports and report for those. +function resolveFileDeep(helpers, parentCache, source, fullPath) { + var resolvers = helpers.resolvers; + var readFile = helpers.readFile; + + var contexts = parentCache.contexts; + var sources = parentCache.sources; + + contexts = contexts || {}; + var nestResolve = resolveFileDeep.bind(null, helpers, parentCache, null); + var context = path.dirname(fullPath); + readFile = whenNodefn.lift(readFile); + + return when + .resolve(source || sources[fullPath] || readFile(fullPath)) + // Cast the buffer from the cached input file system to a string. + .then(String) + // Store the source so that the evaluator doesn't need to touch the + // file system. + .then(function(_source) { + sources[fullPath] = _source; + return _source; + }) + // Make sure the stylus functions/index.styl source is stored. + .then(partial(ensureFunctionsSource, sources)) + // List imports and use its cache. The source file is translated into a + // list of imports. Where the source file came from isn't important for the + // list. The where is added by resolveMany with the context and resolvers. + .then(partialRight(listImports, { cache: parentCache.imports })) + .then(resolveMany.bind(null, resolvers, context)) + .then(function(newPaths) { + // Contexts are the full path since multiple could be in the same folder + // but different deps. + contexts[context] = merge(contexts[context] || {}, newPaths); + return when.map(Object.keys(newPaths), function(key) { + var found = newPaths[key] && newPaths[key].path; + if (found) { + return when.map(found, nestResolve); + } + }); + }) + .then(function() { + return PathCache.create(contexts, sources, parentCache.imports); + }); +} + +// Resolve functions in a promise wrapper to catch any errors from resolving. +var functionsPath = + new when.Promise(function(resolve) { + resolve(require.resolve('stylus/lib/functions/index.styl')); + }) + .catch(function() { return ''; }); + +var functionsSource = functionsPath + .then(readFile) + .catch(function(error) { + // Ignore error if functions/index.styl doesn't exist. + if (error.code !== 'ENOENT') { + throw error; + } + return ''; + }) + .then(String); + +function ensureFunctionsSource(sources, source) { + if (!sources[functionsPath]) { + return functionsSource + .then(function(functionsSource) { + if (functionsSource) { + sources[functionsPath] = functionsSource; + } + }) + // Pass through the source given to this function. + .yield(source); + } + // Pass through the source given to this function. + return source; +} + +var slice = Array.prototype.slice.call.bind(Array.prototype.slice); + +function merge(a, b) { + var key; + for (key in b) { + a[key] = b[key]; + } + return a; +} + +function partial(fn) { + var args = slice(arguments, 1); + return function() { + return fn.apply(this, args.concat(slice(arguments))); + }; +} + +function partialRight(fn) { + var args = slice(arguments, 1); + return function() { + return fn.apply(this, slice(arguments).concat(args)); + }; +} + +function normalizePaths(paths) { + for(var i in paths) { + paths[i] = path.normalize(paths[i]); + } + return paths; +}
\ No newline at end of file |
