index.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. 'use strict'
  2. const path = require('path')
  3. const loaderUtils = require('loader-utils')
  4. const parseOptions = require('./options')
  5. const validateOptions = require('schema-utils')
  6. const postcss = require('postcss')
  7. const postcssrc = require('postcss-load-config')
  8. const SyntaxError = require('./Error')
  9. /**
  10. * PostCSS Loader
  11. *
  12. * > Loads && processes CSS with [PostCSS](https://github.com/postcss/postcss)
  13. *
  14. * @author Andrey Sitnik (@ai) <andrey@sitnik.ru>
  15. *
  16. * @license MIT
  17. * @version 2.0.0
  18. *
  19. * @requires path
  20. *
  21. * @requires loader-utils
  22. * @requires schema-utils
  23. *
  24. * @requires postcss
  25. * @requires postcss-load-config
  26. *
  27. * @requires Error
  28. *
  29. * @method loader
  30. *
  31. * @param {String} css Source
  32. * @param {Object} map Source Map
  33. *
  34. * @return {cb} cb Result
  35. */
  36. module.exports = function loader (css, map, meta) {
  37. const options = Object.assign({}, loaderUtils.getOptions(this))
  38. validateOptions(require('./options.json'), options, 'PostCSS Loader')
  39. const cb = this.async()
  40. const file = this.resourcePath
  41. const sourceMap = options.sourceMap
  42. Promise.resolve().then(() => {
  43. const length = Object.keys(options)
  44. .filter((option) => {
  45. switch (option) {
  46. // case 'exec':
  47. case 'ident':
  48. case 'config':
  49. case 'sourceMap':
  50. return
  51. default:
  52. return option
  53. }
  54. })
  55. .length
  56. if (length) {
  57. return parseOptions.call(this, options)
  58. }
  59. const rc = {
  60. path: path.dirname(file),
  61. ctx: {
  62. file: {
  63. extname: path.extname(file),
  64. dirname: path.dirname(file),
  65. basename: path.basename(file)
  66. },
  67. options: {}
  68. }
  69. }
  70. if (options.config) {
  71. if (options.config.path) {
  72. rc.path = path.resolve(options.config.path)
  73. }
  74. if (options.config.ctx) {
  75. rc.ctx.options = options.config.ctx
  76. }
  77. }
  78. rc.ctx.webpack = this;
  79. return postcssrc(rc.ctx, rc.path)
  80. }).then((config) => {
  81. if (!config) config = {}
  82. if (config.file) this.addDependency(config.file)
  83. // Disable override `to` option from `postcss.config.js`
  84. if (config.options.to) delete config.options.to
  85. // Disable override `from` option from `postcss.config.js`
  86. if (config.options.from) delete config.options.from
  87. let plugins = config.plugins || []
  88. let options = Object.assign({
  89. from: file,
  90. map: sourceMap
  91. ? sourceMap === 'inline'
  92. ? { inline: true, annotation: false }
  93. : { inline: false, annotation: false }
  94. : false
  95. }, config.options)
  96. // Loader Exec (Deprecated)
  97. // https://webpack.js.org/api/loaders/#deprecated-context-properties
  98. if (options.parser === 'postcss-js') {
  99. css = this.exec(css, this.resource)
  100. }
  101. if (typeof options.parser === 'string') {
  102. options.parser = require(options.parser)
  103. }
  104. if (typeof options.syntax === 'string') {
  105. options.syntax = require(options.syntax)
  106. }
  107. if (typeof options.stringifier === 'string') {
  108. options.stringifier = require(options.stringifier)
  109. }
  110. // Loader API Exec (Deprecated)
  111. // https://webpack.js.org/api/loaders/#deprecated-context-properties
  112. if (config.exec) {
  113. css = this.exec(css, this.resource)
  114. }
  115. if (sourceMap && typeof map === 'string') map = JSON.parse(map)
  116. if (sourceMap && map) options.map.prev = map
  117. return postcss(plugins)
  118. .process(css, options)
  119. .then((result) => {
  120. result.warnings().forEach((msg) => this.emitWarning(msg.toString()))
  121. result.messages.forEach((msg) => {
  122. if (msg.type === 'dependency') this.addDependency(msg.file)
  123. })
  124. css = result.css
  125. map = result.map ? result.map.toJSON() : null
  126. if (map) {
  127. map.file = path.resolve(map.file)
  128. map.sources = map.sources.map((src) => path.resolve(src))
  129. }
  130. if (!meta) meta = {}
  131. meta.ast = { 'type': 'postcss', root: result.root }
  132. meta.messages = result.messages
  133. if (this.loaderIndex === 0) {
  134. /**
  135. * @memberof loader
  136. * @callback cb
  137. *
  138. * @param {Object} null Error
  139. * @param {String} result Result (JS Module)
  140. * @param {Object} map Source Map
  141. */
  142. cb(null, `module.exports = ${JSON.stringify(css)}`, map)
  143. return null
  144. }
  145. /**
  146. * @memberof loader
  147. * @callback cb
  148. *
  149. * @param {Object} null Error
  150. * @param {String} css Result (Raw Module)
  151. * @param {Object} map Source Map
  152. */
  153. cb(null, css, map, meta)
  154. return null
  155. })
  156. }).catch((err) => {
  157. if (err.file) this.addDependency(err.file)
  158. return err.name === 'CssSyntaxError' ? cb(new SyntaxError(err)) : cb(err)
  159. })
  160. }