pitcher.js 4.7 KB

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