pitcher.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. const qs = require('querystring')
  2. const loaderUtils = require('loader-utils')
  3. const hash = require('hash-sum')
  4. const selfPath = require.resolve('../index')
  5. const templateLoaderPath = require.resolve('./templateLoader')
  6. const stylePostLoaderPath = require.resolve('./stylePostLoader')
  7. const { resolveCompiler } = require('../compiler')
  8. const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path)
  9. const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path)
  10. const isCSSLoader = (l) => /(\/|\\|@)css-loader/.test(l.path)
  11. const isCacheLoader = (l) => /(\/|\\|@)cache-loader/.test(l.path)
  12. const isPitcher = (l) => l.path !== __filename
  13. const isPreLoader = (l) => !l.pitchExecuted
  14. const isPostLoader = (l) => l.pitchExecuted
  15. const dedupeESLintLoader = (loaders) => {
  16. const res = []
  17. let seen = false
  18. loaders.forEach((l) => {
  19. if (!isESLintLoader(l)) {
  20. res.push(l)
  21. } else if (!seen) {
  22. seen = true
  23. res.push(l)
  24. }
  25. })
  26. return res
  27. }
  28. const shouldIgnoreCustomBlock = (loaders) => {
  29. const actualLoaders = loaders.filter((loader) => {
  30. // vue-loader
  31. if (loader.path === selfPath) {
  32. return false
  33. }
  34. // cache-loader
  35. if (isCacheLoader(loader)) {
  36. return false
  37. }
  38. return true
  39. })
  40. return actualLoaders.length === 0
  41. }
  42. module.exports = (code) => code
  43. // This pitching loader is responsible for intercepting all vue block requests
  44. // and transform it into appropriate requests.
  45. module.exports.pitch = function (remainingRequest) {
  46. const options = loaderUtils.getOptions(this)
  47. const { cacheDirectory, cacheIdentifier } = options
  48. const query = qs.parse(this.resourceQuery.slice(1))
  49. let loaders = this.loaders
  50. // if this is a language block request, eslint-loader may get matched
  51. // multiple times
  52. if (query.type) {
  53. // if this is an inline block, since the whole file itself is being linted,
  54. // remove eslint-loader to avoid duplicate linting.
  55. if (/\.vue$/.test(this.resourcePath)) {
  56. loaders = loaders.filter((l) => !isESLintLoader(l))
  57. } else {
  58. // This is a src import. Just make sure there's not more than 1 instance
  59. // of eslint present.
  60. loaders = dedupeESLintLoader(loaders)
  61. }
  62. }
  63. // remove self
  64. loaders = loaders.filter(isPitcher)
  65. // do not inject if user uses null-loader to void the type (#1239)
  66. if (loaders.some(isNullLoader)) {
  67. return
  68. }
  69. const genRequest = (loaders) => {
  70. // Important: dedupe since both the original rule
  71. // and the cloned rule would match a source import request.
  72. // also make sure to dedupe based on loader path.
  73. // assumes you'd probably never want to apply the same loader on the same
  74. // file twice.
  75. // Exception: in Vue CLI we do need two instances of postcss-loader
  76. // for user config and inline minification. So we need to dedupe baesd on
  77. // path AND query to be safe.
  78. const seen = new Map()
  79. const loaderStrings = []
  80. loaders.forEach((loader) => {
  81. const identifier =
  82. typeof loader === 'string' ? loader : loader.path + loader.query
  83. const request = typeof loader === 'string' ? loader : loader.request
  84. if (!seen.has(identifier)) {
  85. seen.set(identifier, true)
  86. // loader.request contains both the resolved loader path and its options
  87. // query (e.g. ??ref-0)
  88. loaderStrings.push(request)
  89. }
  90. })
  91. return loaderUtils.stringifyRequest(
  92. this,
  93. '-!' +
  94. [...loaderStrings, this.resourcePath + this.resourceQuery].join('!')
  95. )
  96. }
  97. // Inject style-post-loader before css-loader for scoped CSS and trimming
  98. if (query.type === `style`) {
  99. const cssLoaderIndex = loaders.findIndex(isCSSLoader)
  100. if (cssLoaderIndex > -1) {
  101. const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
  102. const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
  103. const request = genRequest([
  104. ...afterLoaders,
  105. stylePostLoaderPath,
  106. ...beforeLoaders
  107. ])
  108. // console.log(request)
  109. return query.module
  110. ? `export { default } from ${request}; export * from ${request}`
  111. : `export * from ${request}`
  112. }
  113. }
  114. // for templates: inject the template compiler & optional cache
  115. if (query.type === `template`) {
  116. const path = require('path')
  117. const cacheLoader =
  118. cacheDirectory && cacheIdentifier
  119. ? [
  120. `${require.resolve('cache-loader')}?${JSON.stringify({
  121. // For some reason, webpack fails to generate consistent hash if we
  122. // use absolute paths here, even though the path is only used in a
  123. // comment. For now we have to ensure cacheDirectory is a relative path.
  124. cacheDirectory: (path.isAbsolute(cacheDirectory)
  125. ? path.relative(process.cwd(), cacheDirectory)
  126. : cacheDirectory
  127. ).replace(/\\/g, '/'),
  128. cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
  129. })}`
  130. ]
  131. : []
  132. const preLoaders = loaders.filter(isPreLoader)
  133. const postLoaders = loaders.filter(isPostLoader)
  134. const { is27 } = resolveCompiler(this.rootContext, this)
  135. const request = genRequest([
  136. ...cacheLoader,
  137. ...postLoaders,
  138. ...(is27 ? [] : [templateLoaderPath + `??vue-loader-options`]),
  139. ...preLoaders
  140. ])
  141. // the template compiler uses esm exports
  142. return `export * from ${request}`
  143. }
  144. // if a custom block has no other matching loader other than vue-loader itself
  145. // or cache-loader, we should ignore it
  146. if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
  147. return ``
  148. }
  149. // When the user defines a rule that has only resourceQuery but no test,
  150. // both that rule and the cloned rule will match, resulting in duplicated
  151. // loaders. Therefore it is necessary to perform a dedupe here.
  152. const request = genRequest(loaders)
  153. return `import mod from ${request}; export default mod; export * from ${request}`
  154. }