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 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)) 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 => { // 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 = [] 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) } }) 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`) { 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 ]) // console.log(request) return `import mod from ${request}; export default mod; 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 request = genRequest([ ...cacheLoader, ...postLoaders, templateLoaderPath + `??vue-loader-options`, ...preLoaders ]) // console.log(request) // 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}` }