css.js 5.8 KB

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