postcss.js 5.2 KB

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