css.js 7.2 KB

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