postcss.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /*
  2. * MIT License http://opensource.org/licenses/MIT
  3. * Author: Ben Holloway @bholloway
  4. */
  5. 'use strict';
  6. var os = require('os'),
  7. path = require('path'),
  8. postcss = require('postcss');
  9. var fileProtocol = require('../file-protocol');
  10. var algerbra = require('../position-algerbra');
  11. var ORPHAN_CR_REGEX = /\r(?!\n)(.|\n)?/g;
  12. /**
  13. * Process the given CSS content into reworked CSS content.
  14. *
  15. * @param {string} sourceFile The absolute path of the file being processed
  16. * @param {string} sourceContent CSS content without source-map
  17. * @param {{outputSourceMap: boolean, transformDeclaration:function, absSourceMap:object,
  18. * sourceMapConsumer:object, removeCR:boolean}} params Named parameters
  19. * @return {{content: string, map: object}} Reworked CSS and optional source-map
  20. */
  21. function process(sourceFile, sourceContent, params) {
  22. // #107 libsass emits orphan CR not considered newline, postcss does consider newline (content vs source-map mismatch)
  23. var correctedContent = params.removeCR && (os.EOL !== '\r') ?
  24. sourceContent.replace(ORPHAN_CR_REGEX, ' $1') :
  25. sourceContent;
  26. // prepend file protocol to all sources to avoid problems with source map
  27. return postcss([
  28. postcss.plugin('postcss-resolve-url', postcssPlugin)
  29. ])
  30. .process(correctedContent, {
  31. from: fileProtocol.prepend(sourceFile),
  32. map : params.outputSourceMap && {
  33. prev : !!params.absSourceMap && fileProtocol.prepend(params.absSourceMap),
  34. inline : false,
  35. annotation : false,
  36. sourcesContent: true // #98 sourcesContent missing from output map
  37. }
  38. })
  39. .then(result => ({
  40. content: result.css,
  41. map : params.outputSourceMap ? fileProtocol.remove(result.map.toJSON()) : null
  42. }));
  43. /**
  44. * Plugin for postcss that follows SASS transpilation.
  45. */
  46. function postcssPlugin() {
  47. return function applyPlugin(styles) {
  48. styles.walkDecls(eachDeclaration);
  49. };
  50. /**
  51. * Process a declaration from the syntax tree.
  52. * @param declaration
  53. */
  54. function eachDeclaration(declaration) {
  55. var prefix,
  56. isValid = declaration.value && (declaration.value.indexOf('url') >= 0);
  57. if (isValid) {
  58. prefix = declaration.prop + declaration.raws.between;
  59. declaration.value = params.transformDeclaration(declaration.value, getPathsAtChar);
  60. }
  61. /**
  62. * Create a hash of base path strings.
  63. *
  64. * Position in the declaration is supported by postcss at the position of the url() statement.
  65. *
  66. * @param {number} index Index in the declaration value at which to evaluate
  67. * @throws Error on invalid source map
  68. * @returns {{subString:string, value:string, property:string, selector:string}} Hash of base path strings
  69. */
  70. function getPathsAtChar(index) {
  71. var subString = declaration.value.slice(0, index),
  72. posSelector = algerbra.sanitise(declaration.parent.source.start),
  73. posProperty = algerbra.sanitise(declaration.source.start),
  74. posValue = algerbra.add([posProperty, algerbra.strToOffset(prefix)]),
  75. posSubString = algerbra.add([posValue, algerbra.strToOffset(subString)]);
  76. var result = {
  77. subString: positionToOriginalDirectory(posSubString),
  78. value : positionToOriginalDirectory(posValue),
  79. property : positionToOriginalDirectory(posProperty),
  80. selector : positionToOriginalDirectory(posSelector)
  81. };
  82. var isValid = [result.subString, result.value, result.property, result.selector].every(Boolean);
  83. if (isValid) {
  84. return result;
  85. }
  86. else if (params.sourceMapConsumer) {
  87. throw new Error(
  88. 'source-map information is not available at url() declaration ' +
  89. (ORPHAN_CR_REGEX.test(sourceContent) ? '(found orphan CR, try removeCR option)' : '(no orphan CR found)')
  90. );
  91. } else {
  92. throw new Error('a valid source-map is not present (ensure preceding loaders output a source-map)');
  93. }
  94. }
  95. }
  96. }
  97. /**
  98. * Given an apparent position find the directory of the original file.
  99. *
  100. * @param startPosApparent {{line: number, column: number}}
  101. * @returns {false|string} Directory of original file or false on invalid
  102. */
  103. function positionToOriginalDirectory(startPosApparent) {
  104. // reverse the original source-map to find the original source file before transpilation
  105. var startPosOriginal =
  106. !!params.sourceMapConsumer &&
  107. params.sourceMapConsumer.originalPositionFor(startPosApparent);
  108. // we require a valid directory for the specified file
  109. var directory =
  110. !!startPosOriginal &&
  111. !!startPosOriginal.source &&
  112. fileProtocol.remove(path.dirname(startPosOriginal.source));
  113. return directory;
  114. }
  115. }
  116. module.exports = process;