UNPKG

stylesheet-deps

Version:

Walk the dependency graph of a stylesheet.

182 lines (139 loc) 5.19 kB
const fs = require('fs'); const path = require('path'); const Transform = require('stream').Transform; const unquote = require('unquote'); const Url = require('url'); class Depper extends Transform { constructor(options) { options = options || {}; options.objectMode = true; super(options); this.syntax = options.syntax || 'css'; this.gonzales = require('gonzales-pe'); this.visited = new Map(); } _transform(chunk, encoding, callback) { let self = this; let resolveData = function (data, file, parent) { let processNode = function (node) { let uri = unquote(node.content); let url = Url.parse(uri, false, true); let dependency = null; let shouldBeReturnedAsIs = function() { // if the url consists of only a hash, it is a reference to an id if (url.hash) { if (!url.path) { return true; } } // if the url host is set, it is a remote uri if (url.host) { return true; } return false; }; if (shouldBeReturnedAsIs()) { dependency = uri; } else { uri = url.pathname; if (self.syntax !== 'css') { let ext = '.' + self.syntax; if (!path.extname(uri)) { uri += ext; } } if (path.isAbsolute(uri)) { dependency = uri; } else { dependency = path.resolve(path.dirname(file), uri); } } resolveFile(dependency, file); }; let parseTree = null; try { parseTree = self.gonzales.parse(data.toString(), { syntax: self.syntax }); } catch (err) { // noop, we continue } if (parseTree) { parseTree.traverseByType('atrule', function (node) { let atKeywordNode = node.first('atkeyword'); let identNode = atKeywordNode.first('ident'); if (identNode.content === 'import') { let stringNode = node.first('string'); processNode(stringNode); } }); switch (self.syntax) { case 'css': { parseTree.traverseByType('uri', function (node) { let stringNode = node.first('string'); if (!stringNode) { stringNode = node.first('raw'); } processNode(stringNode); }); break; } } } }; let resolveFile = function (file, parent) { if (!self.visited.has(file)) { self.visited.set(file, true); let candidates = [file]; switch (self.syntax) { case 'sass': case 'scss': { let basename = path.basename(file); let dirname = path.dirname(file); basename = '_' + basename; let candidate = path.join(dirname, basename); candidates.push(candidate); } } let missing = false; let data = null; let candidate = candidates.find(function (candidate) { try { data = fs.readFileSync(candidate); return true; } catch (err) { // noop, we just catch this } }); if (!candidate) { candidates.forEach(function (candidate) { self.emit('missing', candidate, parent); }); } else { self.push(candidate); resolveData(data, candidate); } } }; let file = chunk.toString(); if (this.inlineSource) { resolveData(this.inlineSource, file); } else { resolveFile(file, null); } callback(); } inline(source, basedir, callback) { this.inlineSource = source; let inlineName = '__INLINE__' + Math.random(); let file = path.resolve(basedir || process.cwd(), inlineName); this.write(file, 'utf8', callback); } } module.exports = Depper;