123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- var SourceMapGenerator = require('source-map').SourceMapGenerator;
- var SourceNode = require('source-map').SourceNode;
- // Our own implementation of SourceNode#toStringWithSourceMap,
- // since SourceNode doesn't allow multiple references to original source.
- // Also, as we know structure of result we could be optimize generation
- // (currently it's ~40% faster).
- function walk(node, fn) {
- for (var chunk, i = 0; i < node.children.length; i++) {
- chunk = node.children[i];
- if (chunk instanceof SourceNode) {
- // this is a hack, because source maps doesn't support for 1(generated):N(original)
- // if (chunk.merged) {
- // fn('', chunk);
- // }
- walk(chunk, fn);
- } else {
- fn(chunk, node);
- }
- }
- }
- function generateSourceMap(root) {
- var map = new SourceMapGenerator();
- var css = '';
- var sourceMappingActive = false;
- var lastOriginalLine = null;
- var lastOriginalColumn = null;
- var lastIndexOfNewline;
- var generated = {
- line: 1,
- column: 0
- };
- var activatedMapping = {
- generated: generated
- };
- walk(root, function(chunk, original) {
- if (original.line !== null &&
- original.column !== null) {
- if (lastOriginalLine !== original.line ||
- lastOriginalColumn !== original.column) {
- map.addMapping({
- source: original.source,
- original: original,
- generated: generated
- });
- }
- lastOriginalLine = original.line;
- lastOriginalColumn = original.column;
- sourceMappingActive = true;
- } else if (sourceMappingActive) {
- map.addMapping(activatedMapping);
- sourceMappingActive = false;
- }
- css += chunk;
- lastIndexOfNewline = chunk.lastIndexOf('\n');
- if (lastIndexOfNewline !== -1) {
- generated.line += chunk.match(/\n/g).length;
- generated.column = chunk.length - lastIndexOfNewline - 1;
- } else {
- generated.column += chunk.length;
- }
- });
- return {
- css: css,
- map: map
- };
- }
- function createAnonymousSourceNode(children) {
- return new SourceNode(
- null,
- null,
- null,
- children
- );
- }
- function createSourceNode(info, children) {
- if (info.primary) {
- // special marker node to add several references to original
- // var merged = createSourceNode(info.merged, []);
- // merged.merged = true;
- // children.unshift(merged);
- // use recursion, because primary can also has a primary/merged info
- return createSourceNode(info.primary, children);
- }
- return new SourceNode(
- info.line,
- info.column - 1,
- info.source,
- children
- );
- }
- function each(list) {
- if (list.head === null) {
- return '';
- }
- if (list.head === list.tail) {
- return translate(list.head.data);
- }
- return list.map(translate).join('');
- }
- function eachDelim(list, delimeter) {
- if (list.head === null) {
- return '';
- }
- if (list.head === list.tail) {
- return translate(list.head.data);
- }
- return list.map(translate).join(delimeter);
- }
- function translate(node) {
- switch (node.type) {
- case 'StyleSheet':
- return createAnonymousSourceNode(node.rules.map(translate));
- case 'Atrule':
- var nodes = ['@', node.name];
- if (node.expression && !node.expression.sequence.isEmpty()) {
- nodes.push(' ', translate(node.expression));
- }
- if (node.block) {
- nodes.push('{', translate(node.block), '}');
- } else {
- nodes.push(';');
- }
- return createSourceNode(node.info, nodes);
- case 'Ruleset':
- return createAnonymousSourceNode([
- translate(node.selector), '{', translate(node.block), '}'
- ]);
- case 'Selector':
- return createAnonymousSourceNode(node.selectors.map(translate)).join(',');
- case 'SimpleSelector':
- var nodes = node.sequence.map(function(node) {
- // add extra spaces around /deep/ combinator since comment beginning/ending may to be produced
- if (node.type === 'Combinator' && node.name === '/deep/') {
- return ' ' + translate(node) + ' ';
- }
- return translate(node);
- });
- return createSourceNode(node.info, nodes);
- case 'Block':
- return createAnonymousSourceNode(node.declarations.map(translate)).join(';');
- case 'Declaration':
- return createSourceNode(
- node.info,
- [translate(node.property), ':', translate(node.value)]
- );
- case 'Property':
- return node.name;
- case 'Value':
- return node.important
- ? each(node.sequence) + '!important'
- : each(node.sequence);
- case 'Attribute':
- var result = translate(node.name);
- var flagsPrefix = ' ';
- if (node.operator !== null) {
- result += node.operator;
- if (node.value !== null) {
- result += translate(node.value);
- // space between string and flags is not required
- if (node.value.type === 'String') {
- flagsPrefix = '';
- }
- }
- }
- if (node.flags !== null) {
- result += flagsPrefix + node.flags;
- }
- return '[' + result + ']';
- case 'FunctionalPseudo':
- return ':' + node.name + '(' + eachDelim(node.arguments, ',') + ')';
- case 'Function':
- return node.name + '(' + eachDelim(node.arguments, ',') + ')';
- case 'Negation':
- return ':not(' + eachDelim(node.sequence, ',') + ')';
- case 'Braces':
- return node.open + each(node.sequence) + node.close;
- case 'Argument':
- case 'AtruleExpression':
- return each(node.sequence);
- case 'Url':
- return 'url(' + translate(node.value) + ')';
- case 'Progid':
- return translate(node.value);
- case 'Combinator':
- return node.name;
- case 'Identifier':
- return node.name;
- case 'PseudoClass':
- return ':' + node.name;
- case 'PseudoElement':
- return '::' + node.name;
- case 'Class':
- return '.' + node.name;
- case 'Id':
- return '#' + node.name;
- case 'Hash':
- return '#' + node.value;
- case 'Dimension':
- return node.value + node.unit;
- case 'Nth':
- return node.value;
- case 'Number':
- return node.value;
- case 'String':
- return node.value;
- case 'Operator':
- return node.value;
- case 'Raw':
- return node.value;
- case 'Unknown':
- return node.value;
- case 'Percentage':
- return node.value + '%';
- case 'Space':
- return ' ';
- case 'Comment':
- return '/*' + node.value + '*/';
- default:
- throw new Error('Unknown node type: ' + node.type);
- }
- }
- module.exports = function(node) {
- return generateSourceMap(
- createAnonymousSourceNode(translate(node))
- );
- };
|