css.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. const fs = require('fs')
  2. const path = require('path')
  3. const semver = require('semver')
  4. const findExisting = (context, files) => {
  5. for (const file of files) {
  6. if (fs.existsSync(path.join(context, file))) {
  7. return file
  8. }
  9. }
  10. }
  11. module.exports = (api, options) => {
  12. api.chainWebpack(webpackConfig => {
  13. const getAssetPath = require('../util/getAssetPath')
  14. const shadowMode = !!process.env.VUE_CLI_CSS_SHADOW_MODE
  15. const isProd = process.env.NODE_ENV === 'production'
  16. let sassLoaderVersion
  17. try {
  18. sassLoaderVersion = semver.major(require('sass-loader/package.json').version)
  19. } catch (e) {}
  20. const defaultSassLoaderOptions = {}
  21. try {
  22. defaultSassLoaderOptions.implementation = require('sass')
  23. // since sass-loader 8, fibers will be automatically detected and used
  24. if (sassLoaderVersion < 8) {
  25. defaultSassLoaderOptions.fiber = require('fibers')
  26. }
  27. } catch (e) {}
  28. const {
  29. modules = false,
  30. extract = isProd,
  31. sourceMap = false,
  32. loaderOptions = {}
  33. } = options.css || {}
  34. const shouldExtract = extract !== false && !shadowMode
  35. const filename = getAssetPath(
  36. options,
  37. `css/[name]${options.filenameHashing ? '.[contenthash:8]' : ''}.css`
  38. )
  39. const extractOptions = Object.assign({
  40. filename,
  41. chunkFilename: filename
  42. }, extract && typeof extract === 'object' ? extract : {})
  43. // use relative publicPath in extracted CSS based on extract location
  44. const cssPublicPath = process.env.VUE_CLI_BUILD_TARGET === 'lib'
  45. // in lib mode, CSS is extracted to dist root.
  46. ? './'
  47. : '../'.repeat(
  48. extractOptions.filename
  49. .replace(/^\.[\/\\]/, '')
  50. .split(/[\/\\]/g)
  51. .length - 1
  52. )
  53. // check if the project has a valid postcss config
  54. // if it doesn't, don't use postcss-loader for direct style imports
  55. // because otherwise it would throw error when attempting to load postcss config
  56. const hasPostCSSConfig = !!(loaderOptions.postcss || api.service.pkg.postcss || findExisting(api.resolve('.'), [
  57. '.postcssrc',
  58. '.postcssrc.js',
  59. 'postcss.config.js',
  60. '.postcssrc.yaml',
  61. '.postcssrc.json'
  62. ]))
  63. // if building for production but not extracting CSS, we need to minimize
  64. // the embbeded inline CSS as they will not be going through the optimizing
  65. // plugin.
  66. const needInlineMinification = isProd && !shouldExtract
  67. const cssnanoOptions = {
  68. preset: ['default', {
  69. mergeLonghand: false,
  70. cssDeclarationSorter: false
  71. }]
  72. }
  73. if (options.productionSourceMap && sourceMap) {
  74. cssnanoOptions.map = { inline: false }
  75. }
  76. function createCSSRule (lang, test, loader, options) {
  77. const baseRule = webpackConfig.module.rule(lang).test(test)
  78. // rules for <style lang="module">
  79. const vueModulesRule = baseRule.oneOf('vue-modules').resourceQuery(/module/)
  80. applyLoaders(vueModulesRule, true)
  81. // rules for <style>
  82. const vueNormalRule = baseRule.oneOf('vue').resourceQuery(/\?vue/)
  83. applyLoaders(vueNormalRule, false)
  84. // rules for *.module.* files
  85. const extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/)
  86. applyLoaders(extModulesRule, true)
  87. // rules for normal CSS imports
  88. const normalRule = baseRule.oneOf('normal')
  89. applyLoaders(normalRule, modules)
  90. function applyLoaders (rule, modules) {
  91. if (shouldExtract) {
  92. rule
  93. .use('extract-css-loader')
  94. .loader(require('mini-css-extract-plugin').loader)
  95. .options({
  96. hmr: !isProd,
  97. publicPath: cssPublicPath
  98. })
  99. } else {
  100. rule
  101. .use('vue-style-loader')
  102. .loader('vue-style-loader')
  103. .options({
  104. sourceMap,
  105. shadowMode
  106. })
  107. }
  108. const cssLoaderOptions = Object.assign({
  109. sourceMap,
  110. importLoaders: (
  111. 1 + // stylePostLoader injected by vue-loader
  112. (hasPostCSSConfig ? 1 : 0) +
  113. (needInlineMinification ? 1 : 0)
  114. )
  115. }, loaderOptions.css)
  116. if (modules) {
  117. const {
  118. localIdentName = '[name]_[local]_[hash:base64:5]'
  119. } = loaderOptions.css || {}
  120. Object.assign(cssLoaderOptions, {
  121. modules,
  122. localIdentName
  123. })
  124. }
  125. rule
  126. .use('css-loader')
  127. .loader('css-loader')
  128. .options(cssLoaderOptions)
  129. if (needInlineMinification) {
  130. rule
  131. .use('cssnano')
  132. .loader('postcss-loader')
  133. .options({
  134. sourceMap,
  135. plugins: [require('cssnano')(cssnanoOptions)]
  136. })
  137. }
  138. if (hasPostCSSConfig) {
  139. rule
  140. .use('postcss-loader')
  141. .loader('postcss-loader')
  142. .options(Object.assign({ sourceMap }, loaderOptions.postcss))
  143. }
  144. if (loader) {
  145. rule
  146. .use(loader)
  147. .loader(loader)
  148. .options(Object.assign({ sourceMap }, options))
  149. }
  150. }
  151. }
  152. createCSSRule('css', /\.css$/)
  153. createCSSRule('postcss', /\.p(ost)?css$/)
  154. createCSSRule('scss', /\.scss$/, 'sass-loader', Object.assign(
  155. {},
  156. defaultSassLoaderOptions,
  157. loaderOptions.scss || loaderOptions.sass
  158. ))
  159. if (sassLoaderVersion < 8) {
  160. createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(
  161. {},
  162. defaultSassLoaderOptions,
  163. {
  164. indentedSyntax: true
  165. },
  166. loaderOptions.sass
  167. ))
  168. } else {
  169. createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(
  170. {},
  171. defaultSassLoaderOptions,
  172. loaderOptions.sass,
  173. {
  174. sassOptions: Object.assign(
  175. {},
  176. loaderOptions.sass && loaderOptions.sass.sassOptions,
  177. {
  178. indentedSyntax: true
  179. }
  180. )
  181. }
  182. ))
  183. }
  184. createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
  185. createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
  186. preferPathResolver: 'webpack'
  187. }, loaderOptions.stylus))
  188. // inject CSS extraction plugin
  189. if (shouldExtract) {
  190. webpackConfig
  191. .plugin('extract-css')
  192. .use(require('mini-css-extract-plugin'), [extractOptions])
  193. // minify extracted CSS
  194. if (isProd) {
  195. webpackConfig
  196. .plugin('optimize-css')
  197. .use(require('@intervolga/optimize-cssnano-plugin'), [{
  198. sourceMap: options.productionSourceMap && sourceMap,
  199. cssnanoOptions
  200. }])
  201. }
  202. }
  203. })
  204. }