diff options
Diffstat (limited to 'node_modules/stylus-loader/lib')
| -rw-r--r-- | node_modules/stylus-loader/lib/evaluator.js | 166 | ||||
| -rw-r--r-- | node_modules/stylus-loader/lib/listimports.js | 80 | ||||
| -rw-r--r-- | node_modules/stylus-loader/lib/pathcache.js | 304 | ||||
| -rw-r--r-- | node_modules/stylus-loader/lib/resolver.js | 112 |
4 files changed, 662 insertions, 0 deletions
diff --git a/node_modules/stylus-loader/lib/evaluator.js b/node_modules/stylus-loader/lib/evaluator.js new file mode 100644 index 00000000..dc6013d8 --- /dev/null +++ b/node_modules/stylus-loader/lib/evaluator.js @@ -0,0 +1,166 @@ +// Except for a block in #visitImport, everything here is an exact copy of the +// source in stylus this currently depends on. If stylus is updated, update +// this appropriately. + +var Evaluator = require('stylus/lib/visitor/evaluator') + , nodes = require('stylus/lib/nodes') + , Stack = require('stylus/lib/stack') + , Frame = require('stylus/lib/stack/frame') + , Scope = require('stylus/lib/stack/scope') + , utils = require('stylus/lib/utils') + , bifs = require('stylus/lib/functions') + , basename = require('path').basename + , dirname = require('path').dirname + , relative = require('path').relative + , join = require('path').join + , colors = require('stylus/lib/colors') + // , debug = require('debug')('stylus:evaluator') + , fs = require('fs'); + +module.exports = CachedPathEvaluator; + +/** + * Import `file` and return Block node. + * + * @api private + */ +function importFile(node, file, literal, index) { + var importStack = this.importStack + , Parser = require('stylus/lib/parser') + , stat; + + // Handling the `require` + if (node.once) { + if (this.requireHistory[file]) return nodes.null; + this.requireHistory[file] = true; + + if (literal && !this.includeCSS) { + return node; + } + } + + // Expose imports + node.path = file; + node.dirname = dirname(file); + // Store the modified time + stat = fs.statSync(file); + node.mtime = stat.mtime; + this.paths.push(node.dirname); + + // Avoid overflows from importing the same file over again + if (file === importStack[importStack.length - 1]) return nodes.null; + + if (this.options._imports) this.options._imports.push(node.clone()); + + // Parse the file + importStack.push(file); + nodes.filename = file; + + var str; + if (this.cache.sources && this.cache.sources[file]) { + str = this.cache.sources[file]; + } else { + str = fs.readFileSync(file, 'utf8'); + } + + if (literal && !this.resolveURL) return new nodes.Literal(str.replace(/\r\n?/g, '\n')); + + // parse + var block = new nodes.Block + , parser = new Parser(str, utils.merge({ root: block }, this.options)); + + try { + block = parser.parse(); + } catch (err) { + err.filename = file; + err.lineno = parser.lexer.lineno; + err.input = str; + throw err; + } + + // Evaluate imported "root" + block.parent = this.root; + block.scope = false; + var ret = this.visit(block); + importStack.pop(); + if (importStack.length || index) this.paths.pop(); + + return ret; +} + +function CachedPathEvaluator(root, options) { + Evaluator.apply(this, arguments); + + this.cache = options.cache; +} + +CachedPathEvaluator.prototype = Object.create(Evaluator.prototype); +CachedPathEvaluator.prototype.constructor = CachedPathEvaluator; + +CachedPathEvaluator.prototype.visitImport = function(imported) { + this.return++; + + var path = this.visit(imported.path).first + , nodeName = imported.once ? 'require' : 'import' + , found + , literal + , index; + + this.return--; + // debug('import %s', path); + + // url() passed + if ('url' == path.name) { + if (imported.once) throw new Error('You cannot @require a url'); + + return imported; + } + + // Ensure string + if (!path.string) throw new Error('@' + nodeName + ' string expected'); + + var name = path = path.string; + + // Absolute URL + if (/url\s*\(\s*['"]?(?:https?:)?\/\//i.test(path)) { + if (imported.once) throw new Error('You cannot @require a url'); + return imported; + } + + // Literal + if (/\.css(?:"|$)/.test(path)) { + literal = true; + if (!imported.once && !this.includeCSS) { + return imported; + } + } + + // support optional .styl + if (!literal && !/\.styl$/i.test(path)) path += '.styl'; + + /***************************************************************************** + * THIS IS THE ONLY BLOCK THAT DIFFERS FROM THE ACTUAL STYLUS IMPLEMENTATION. * + *****************************************************************************/ + // Lookup + var dirname = this.paths[this.paths.length - 1]; + found = this.cache.find(path, dirname); + index = this.cache.isIndex(path, dirname); + if (!found) { + found = utils.find(path, this.paths, this.filename); + if (!found) { + found = utils.lookupIndex(name, this.paths, this.filename); + index = true; + } + } + + // Throw if import failed + if (!found) throw new Error('failed to locate @' + nodeName + ' file ' + path); + + var block = new nodes.Block; + + for (var i = 0, len = found.length; i < len; ++i) { + block.push(importFile.call(this, imported, found[i], literal, index)); + } + + return block; +} diff --git a/node_modules/stylus-loader/lib/listimports.js b/node_modules/stylus-loader/lib/listimports.js new file mode 100644 index 00000000..bcf9540f --- /dev/null +++ b/node_modules/stylus-loader/lib/listimports.js @@ -0,0 +1,80 @@ +var Parser = require('stylus/lib/parser'); +var Visitor = require('stylus/lib/visitor'); +var nodes = require('stylus/lib/nodes'); + +module.exports = listImports; + +// ImportVisitor is a simple stylus ast visitor that navigates the graph +// building a list of imports in it. +function ImportVisitor() { + Visitor.apply(this, arguments); + this.importPaths = []; +} + +ImportVisitor.prototype = Object.create(Visitor.prototype); +ImportVisitor.prototype.constructor = ImportVisitor; + +ImportVisitor.prototype.visitImport = function(node) { + this.importPaths.push(node.path.first.string); + return node; +}; + +ImportVisitor.prototype.visitRoot = function(block){ + for (var i = 0; i < block.nodes.length; ++i) { + this.visit(block.nodes[i]); + } + return block; +}; + +ImportVisitor.prototype.visitExpression = function(expr) { + for (var i = 0; i < expr.nodes.length; ++i) { + this.visit(expr.nodes[i]); + } + return expr; +}; + +ImportVisitor.prototype.visitCall = function(fn) { + if (fn.name === 'use' || fn.name === 'json') { + this.importPaths.push(fn.args.first.string); + } + return fn; +}; + +ImportVisitor.prototype.visitSelector = function(sel) { + for (var i = 0; i < sel.block.nodes.length; i++) { + this.visit(sel.block.nodes[i]); + } + return sel; +} + +ImportVisitor.prototype.visitBlock = ImportVisitor.prototype.visitRoot; +ImportVisitor.prototype.visitGroup = ImportVisitor.prototype.visitRoot; + +// Returns a list of paths that given source imports. +function listImports(source, options) { + // Store source -> imports work in a cache. The Parser is the most expensive + // part of stylus and we can't use their cache without creating undesired side + // effects later during the actual render. In single run builds this will + // benefit repeated files imported like common styling. In multiple run builds + // this will help stylus import trees when a dependency changes, the higher up + // files won't need to be parsed again. + var cache = options.cache; + if (cache && cache[source]) { return cache[source]; } + + // Current idea here is to silence errors and let them rise in stylus's + // renderer which has more handling so that the error message is more + // meaningful and easy to understand. + try { + var ast = new Parser(source, { cache: false }).parse(); + } catch (e) { + return []; + } + var importVisitor = new ImportVisitor(ast, {}); + importVisitor.visit(ast); + + if (cache) { + cache[source] = importVisitor.importPaths; + } + + return importVisitor.importPaths; +} 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 diff --git a/node_modules/stylus-loader/lib/resolver.js b/node_modules/stylus-loader/lib/resolver.js new file mode 100644 index 00000000..c4cb0785 --- /dev/null +++ b/node_modules/stylus-loader/lib/resolver.js @@ -0,0 +1,112 @@ +// This implementation of the url resolver adds awareness for webpack loaders +/* + +Modified from the stylus implementation: + +(The MIT License) + +Copyright (c) 2010–2014 LearnBoost <dev@learnboost.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/*jshint laxcomma:true */ +var Stylus = require('stylus') + , Compiler = Stylus.Compiler + , nodes = Stylus.nodes + , utils = Stylus.utils + , parse = require('url').parse + , relative = require('path').relative + , dirname = require('path').dirname + , extname = require('path').extname + , sep = require('path').sep; + +/** + * Return a url() function with the given `options`. + * + * Options: + * + * - `paths` resolution path(s), merged with general lookup paths + * + * Examples: + * + * stylus(str) + * .set('filename', __dirname + '/css/test.styl') + * .define('url', stylus.resolver({ paths: [__dirname + '/public'] })) + * .render(function(err, css){ ... }) + * + * @param {Object} options + * @return {Function} + * @api public + */ + +module.exports = function(options) { + options = options || {}; + + var _paths = options.paths || []; + + function url(url) { + var paths = _paths.concat(this.paths), + filename = this.filename; + + // Compile the url + var compiler = new Compiler(url); + compiler.isURL = true; + url = url.nodes.map(function(node){ + return compiler.visit(node); + }).join(''); + + function resolveComponent(url) { + if (!url) { + return url; + } + + url = parse(url); + if (url.protocol) { + return url.href; + } + + // Lookup + var found = utils.lookup(url.pathname, paths, '', true); + if (!found) { + return url.href; + } + + var tail = ''; + if (url.search) { + tail += url.search; + } + if (url.hash) { + tail += url.hash; + } + + var res = relative(dirname(filename), found) + tail; + if ('\\' == sep) res = res.replace(/\\/g, '/'); + return res; + } + + var components = url.split(/!/g); + components = components.map(resolveComponent); + return new nodes.Literal('url("' + components.join('!') + '")'); + } + + url.raw = true; + return url; +}; |
