aboutsummaryrefslogtreecommitdiff
path: root/node_modules/stylus-loader/lib/pathcache.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/stylus-loader/lib/pathcache.js')
-rw-r--r--node_modules/stylus-loader/lib/pathcache.js304
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