css.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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. hmr: !isProd,
  89. publicPath: cssPublicPath
  90. })
  91. } else {
  92. rule
  93. .use('vue-style-loader')
  94. .loader('vue-style-loader')
  95. .options({
  96. sourceMap,
  97. shadowMode
  98. })
  99. }
  100. const cssLoaderOptions = Object.assign({
  101. sourceMap,
  102. importLoaders: (
  103. 1 + // stylePostLoader injected by vue-loader
  104. (hasPostCSSConfig ? 1 : 0) +
  105. (needInlineMinification ? 1 : 0)
  106. )
  107. }, loaderOptions.css)
  108. if (modules) {
  109. const {
  110. localIdentName = '[name]_[local]_[hash:base64:5]'
  111. } = loaderOptions.css || {}
  112. Object.assign(cssLoaderOptions, {
  113. modules,
  114. localIdentName
  115. })
  116. }
  117. rule
  118. .use('css-loader')
  119. .loader('css-loader')
  120. .options(cssLoaderOptions)
  121. if (needInlineMinification) {
  122. rule
  123. .use('cssnano')
  124. .loader('postcss-loader')
  125. .options({
  126. sourceMap,
  127. plugins: [require('cssnano')(cssnanoOptions)]
  128. })
  129. }
  130. if (hasPostCSSConfig) {
  131. rule
  132. .use('postcss-loader')
  133. .loader('postcss-loader')
  134. .options(Object.assign({ sourceMap }, loaderOptions.postcss))
  135. }
  136. if (loader) {
  137. rule
  138. .use(loader)
  139. .loader(loader)
  140. .options(Object.assign({ sourceMap }, options))
  141. }
  142. }
  143. }
  144. createCSSRule('css', /\.css$/)
  145. createCSSRule('postcss', /\.p(ost)?css$/)
  146. createCSSRule('scss', /\.scss$/, 'sass-loader', Object.assign(defaultSassLoaderOptions, loaderOptions.sass))
  147. createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(defaultSassLoaderOptions, {
  148. indentedSyntax: true
  149. }, loaderOptions.sass))
  150. createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
  151. createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({
  152. preferPathResolver: 'webpack'
  153. }, loaderOptions.stylus))
  154. // inject CSS extraction plugin
  155. if (shouldExtract) {
  156. webpackConfig
  157. .plugin('extract-css')
  158. .use(require('mini-css-extract-plugin'), [extractOptions])
  159. // minify extracted CSS
  160. if (isProd) {
  161. webpackConfig
  162. .plugin('optimize-css')
  163. .use(require('@intervolga/optimize-cssnano-plugin'), [{
  164. sourceMap: options.productionSourceMap && sourceMap,
  165. cssnanoOptions
  166. }])
  167. }
  168. }
  169. })
  170. }