| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- 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;
- }
|