pitcher.js 5.6 KB

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