diff --git a/lib/loaders/pitcher.js b/lib/loaders/pitcher.js index 5989dbe3..2e432c65 100644 --- a/lib/loaders/pitcher.js +++ b/lib/loaders/pitcher.js @@ -80,30 +80,34 @@ module.exports.pitch = function (remainingRequest) { return } + // 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 loadersSeen = new Set() + loaders = loaders.filter((loader) => { + const identifier = + typeof loader === 'string' ? loader : loader.path + loader.query + if (!loadersSeen.has(identifier)) { + loadersSeen.add(identifier) + return true + } + return false + }) + 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 loaderStrings = loaders.map((loader) => { 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) - } + // loader.request contains both the resolved loader path and its options + // query (e.g. ??ref-0) + return request }) if (enableInlineMatchResource) { return loaderUtils.stringifyRequest( diff --git a/package.json b/package.json index aa8d5755..9125021b 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,9 @@ }, "prettier": { "optional": true + }, + "webpack": { + "optional": true } }, "dependencies": { diff --git a/test/style.spec.js b/test/style.spec.js index 0e35e1a7..f81efd5b 100644 --- a/test/style.spec.js +++ b/test/style.spec.js @@ -3,7 +3,8 @@ const { genId, mockRender, mockBundleAndRun, - DEFAULT_VUE_USE + DEFAULT_VUE_USE, + bundle } = require('./utils') test('scoped style', done => { @@ -224,3 +225,47 @@ test('CSS Modules Extend', async () => { }) }) }) + +const webpack5Test = /^5\./.test((require('webpack').version || '')) ? test : test.skip + +webpack5Test('loaders should also be deduplicated when experiments.css is true', (done) => { + let loaders = [] + bundle({ + entry: 'extract-css.vue', + vue: { experimentalInlineMatchResource: true }, + experiments: { css: true }, + module: { + rules: [ + { + test: /\.stylus$/, + use: ['stylus-loader'], + type: 'css' + } + ] + }, + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap('a', (compilation) => { + compilation.hooks.buildModule.tap('a', (module) => { + if (/[?&]lang=stylus/.test(module.resource)) { + const isPitch = module.loaders.some(item => /pitcher/.test(item.loader)) + if (isPitch) { + return + } + // loaders for stylus after pitch + loaders = [...module.loaders] + } + }) + }) + } + } + ] + }, () => { + const stylusLoaderCount = loaders.filter(item => /stylus-loader/.test(item.loader)).length + expect(stylusLoaderCount).toBe(1) + + done() + }) +}) +