224 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const qs = require('querystring')
 | |
| const loaderUtils = require('loader-utils')
 | |
| const hash = require('hash-sum')
 | |
| const selfPath = require.resolve('../index')
 | |
| const templateLoaderPath = require.resolve('./templateLoader')
 | |
| const stylePostLoaderPath = require.resolve('./stylePostLoader')
 | |
| const { resolveCompiler } = require('../compiler')
 | |
| const { testWebpack5 } = require('../codegen/utils')
 | |
| 
 | |
| const isESLintLoader = (l) => /(\/|\\|@)eslint-loader/.test(l.path)
 | |
| const isNullLoader = (l) => /(\/|\\|@)null-loader/.test(l.path)
 | |
| const isCSSLoader = (l) => /(\/|\\|@)css-loader/.test(l.path)
 | |
| const isCacheLoader = (l) => /(\/|\\|@)cache-loader/.test(l.path)
 | |
| const isPitcher = (l) => l.path !== __filename
 | |
| const isPreLoader = (l) => !l.pitchExecuted
 | |
| const isPostLoader = (l) => l.pitchExecuted
 | |
| 
 | |
| const dedupeESLintLoader = (loaders) => {
 | |
|   const res = []
 | |
|   let seen = false
 | |
|   loaders.forEach((l) => {
 | |
|     if (!isESLintLoader(l)) {
 | |
|       res.push(l)
 | |
|     } else if (!seen) {
 | |
|       seen = true
 | |
|       res.push(l)
 | |
|     }
 | |
|   })
 | |
|   return res
 | |
| }
 | |
| 
 | |
| const shouldIgnoreCustomBlock = (loaders) => {
 | |
|   const actualLoaders = loaders.filter((loader) => {
 | |
|     // vue-loader
 | |
|     if (loader.path === selfPath) {
 | |
|       return false
 | |
|     }
 | |
| 
 | |
|     // cache-loader
 | |
|     if (isCacheLoader(loader)) {
 | |
|       return false
 | |
|     }
 | |
| 
 | |
|     return true
 | |
|   })
 | |
|   return actualLoaders.length === 0
 | |
| }
 | |
| 
 | |
| module.exports = (code) => code
 | |
| 
 | |
| // This pitching loader is responsible for intercepting all vue block requests
 | |
| // and transform it into appropriate requests.
 | |
| module.exports.pitch = function (remainingRequest) {
 | |
|   const options = loaderUtils.getOptions(this)
 | |
|   const { cacheDirectory, cacheIdentifier } = options
 | |
|   const query = qs.parse(this.resourceQuery.slice(1))
 | |
|   const isWebpack5 = testWebpack5(this._compiler)
 | |
| 
 | |
|   let loaders = this.loaders
 | |
| 
 | |
|   // if this is a language block request, eslint-loader may get matched
 | |
|   // multiple times
 | |
|   if (query.type) {
 | |
|     // if this is an inline block, since the whole file itself is being linted,
 | |
|     // remove eslint-loader to avoid duplicate linting.
 | |
|     if (/\.vue$/.test(this.resourcePath)) {
 | |
|       loaders = loaders.filter((l) => !isESLintLoader(l))
 | |
|     } else {
 | |
|       // This is a src import. Just make sure there's not more than 1 instance
 | |
|       // of eslint present.
 | |
|       loaders = dedupeESLintLoader(loaders)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // remove self
 | |
|   loaders = loaders.filter(isPitcher)
 | |
| 
 | |
|   // do not inject if user uses null-loader to void the type (#1239)
 | |
|   if (loaders.some(isNullLoader)) {
 | |
|     return
 | |
|   }
 | |
| 
 | |
|   const genRequest = (loaders, lang) => {
 | |
|     // Important: dedupe since both the original rule
 | |
|     // and the cloned rule would match a source import request.
 | |
|     // also make sure to dedupe based on loader path.
 | |
|     // assumes you'd probably never want to apply the same loader on the same
 | |
|     // file twice.
 | |
|     // Exception: in Vue CLI we do need two instances of postcss-loader
 | |
|     // for user config and inline minification. So we need to dedupe baesd on
 | |
|     // path AND query to be safe.
 | |
|     const seen = new Map()
 | |
|     const loaderStrings = []
 | |
|     const enableInlineMatchResource =
 | |
|       isWebpack5 && options.experimentalInlineMatchResource
 | |
| 
 | |
|     loaders.forEach((loader) => {
 | |
|       const identifier =
 | |
|         typeof loader === 'string' ? loader : loader.path + loader.query
 | |
|       const request = typeof loader === 'string' ? loader : loader.request
 | |
|       if (!seen.has(identifier)) {
 | |
|         seen.set(identifier, true)
 | |
|         // loader.request contains both the resolved loader path and its options
 | |
|         // query (e.g. ??ref-0)
 | |
|         loaderStrings.push(request)
 | |
|       }
 | |
|     })
 | |
|     if (enableInlineMatchResource) {
 | |
|       return loaderUtils.stringifyRequest(
 | |
|         this,
 | |
|         `${this.resourcePath}${lang ? `.${lang}` : ''}${
 | |
|           this.resourceQuery
 | |
|         }!=!-!${[...loaderStrings, this.resourcePath + this.resourceQuery].join('!')}`
 | |
|       )
 | |
|     }
 | |
| 
 | |
|     return loaderUtils.stringifyRequest(
 | |
|       this,
 | |
|       '-!' +
 | |
|         [...loaderStrings, this.resourcePath + this.resourceQuery].join('!')
 | |
|     )
 | |
|   }
 | |
| 
 | |
|   // Inject style-post-loader before css-loader for scoped CSS and trimming
 | |
|   if (query.type === `style`) {
 | |
|     if (isWebpack5 && this._compiler.options.experiments && this._compiler.options.experiments.css) {
 | |
|       // If user enables `experiments.css`, then we are trying to emit css code directly.
 | |
|       // Although we can target requests like `xxx.vue?type=style` to match `type: "css"`,
 | |
|       // it will make the plugin a mess.
 | |
|       if (!options.experimentalInlineMatchResource) {
 | |
|         this.emitError(
 | |
|           new Error(
 | |
|             '`experimentalInlineMatchResource` should be enabled if `experiments.css` enabled currently'
 | |
|           )
 | |
|         )
 | |
|         return ''
 | |
|       }
 | |
| 
 | |
|       if (query.inline || query.module) {
 | |
|         this.emitError(
 | |
|           new Error(
 | |
|             '`inline` or `module` is currently not supported with `experiments.css` enabled'
 | |
|           )
 | |
|         )
 | |
|         return ''
 | |
|       }
 | |
| 
 | |
|       const loaderString = [stylePostLoaderPath, ...loaders]
 | |
|         .map((loader) => {
 | |
|           return typeof loader === 'string' ? loader : loader.request
 | |
|         })
 | |
|         .join('!')
 | |
| 
 | |
|         const styleRequest = loaderUtils.stringifyRequest(
 | |
|           this,
 | |
|           `${this.resourcePath}${query.lang ? `.${query.lang}` : ''}${
 | |
|             this.resourceQuery
 | |
|           }!=!-!${loaderString}!${this.resourcePath + this.resourceQuery}`
 | |
|         )
 | |
|         return `@import ${styleRequest};`
 | |
|     }
 | |
| 
 | |
|     const cssLoaderIndex = loaders.findIndex(isCSSLoader)
 | |
|     if (cssLoaderIndex > -1) {
 | |
|       const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
 | |
|       const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
 | |
|       const request = genRequest(
 | |
|         [...afterLoaders, stylePostLoaderPath, ...beforeLoaders],
 | |
|         query.lang || 'css'
 | |
|       )
 | |
|       // console.log(request)
 | |
|       return query.module
 | |
|         ? `export { default } from  ${request}; export * from ${request}`
 | |
|         : `export * from ${request}`
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // for templates: inject the template compiler & optional cache
 | |
|   if (query.type === `template`) {
 | |
|     const path = require('path')
 | |
|     const cacheLoader =
 | |
|       cacheDirectory && cacheIdentifier
 | |
|         ? [
 | |
|             `${require.resolve('cache-loader')}?${JSON.stringify({
 | |
|               // For some reason, webpack fails to generate consistent hash if we
 | |
|               // use absolute paths here, even though the path is only used in a
 | |
|               // comment. For now we have to ensure cacheDirectory is a relative path.
 | |
|               cacheDirectory: (path.isAbsolute(cacheDirectory)
 | |
|                 ? path.relative(process.cwd(), cacheDirectory)
 | |
|                 : cacheDirectory
 | |
|               ).replace(/\\/g, '/'),
 | |
|               cacheIdentifier: hash(cacheIdentifier) + '-vue-loader-template'
 | |
|             })}`
 | |
|           ]
 | |
|         : []
 | |
| 
 | |
|     const preLoaders = loaders.filter(isPreLoader)
 | |
|     const postLoaders = loaders.filter(isPostLoader)
 | |
|     const { is27 } = resolveCompiler(this.rootContext, this)
 | |
| 
 | |
|     const request = genRequest([
 | |
|       ...cacheLoader,
 | |
|       ...postLoaders,
 | |
|       ...(is27 ? [] : [templateLoaderPath + `??vue-loader-options`]),
 | |
|       ...preLoaders
 | |
|     ])
 | |
| 
 | |
|     // the template compiler uses esm exports
 | |
|     return `export * from ${request}`
 | |
|   }
 | |
| 
 | |
|   // if a custom block has no other matching loader other than vue-loader itself
 | |
|   // or cache-loader, we should ignore it
 | |
|   if (query.type === `custom` && shouldIgnoreCustomBlock(loaders)) {
 | |
|     return ``
 | |
|   }
 | |
| 
 | |
|   // When the user defines a rule that has only resourceQuery but no test,
 | |
|   // both that rule and the cloned rule will match, resulting in duplicated
 | |
|   // loaders. Therefore it is necessary to perform a dedupe here.
 | |
|   const request = genRequest(loaders)
 | |
|   return `import mod from ${request}; export default mod; export * from ${request}`
 | |
| }
 |