css.js 7.5 KB

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