text: const qs = require('querystring')
  const RuleSet = require('webpack/lib/RuleSet')
  
  const id = 'vue-loader-plugin'
  const NS = 'vue-loader'
  
  class VueLoaderPlugin {
    apply (compiler) {
      // add NS marker so that the loader can detect and report missing plugin
      if (compiler.hooks) {
        // webpack 4
        compiler.hooks.compilation.tap(id, compilation => {
          compilation.hooks.normalModuleLoader.tap(id, loaderContext => {
            loaderContext[NS] = true
          })
        })
      } else {
        // webpack < 4
        compiler.plugin('compilation', compilation => {
          compilation.plugin('normal-module-loader', loaderContext => {
            loaderContext[NS] = true
          })
        })
      }
  
      // use webpack's RuleSet utility to normalize user rules
      const rawRules = compiler.options.module.rules
      const { rules } = new RuleSet(rawRules)
  
      // find the rule that applies to vue files
      let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
      if (vueRuleIndex < 0) {
        vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
      }
      const vueRule = rules[vueRuleIndex]
  
      if (!vueRule) {
        throw new Error(
          `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
          `Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
        )
      }
  
      if (vueRule.oneOf) {
        throw new Error(
          `[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
        )
      }
  
      // get the normlized "use" for vue files
      const vueUse = vueRule.use
      // get vue-loader options
      const vueLoaderUseIndex = vueUse.findIndex(u => {
        return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
      })
  
      if (vueLoaderUseIndex < 0) {
        throw new Error(
          `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
          `Make sure the rule matching .vue files include vue-loader in its use.`
        )
      }
  
      // make sure vue-loader options has a known ident so that we can share
      // options by reference in the template-loader by using a ref query like
      // template-loader??vue-loader-options
      const vueLoaderUse = vueUse[vueLoaderUseIndex]
      vueLoaderUse.ident = 'vue-loader-options'
      vueLoaderUse.options = vueLoaderUse.options || {}
  
      // for each user rule (expect the vue rule), create a cloned rule
      // that targets the corresponding language blocks in *.vue files.
      const clonedRules = rules
        .filter(r => r !== vueRule)
        .map(cloneRule)
  
      // global pitcher (responsible for injecting template compiler loader & CSS
      // post loader)
      const pitcher = {
        loader: require.resolve('./loaders/pitcher'),
        resourceQuery: query => {
          const parsed = qs.parse(query.slice(1))
          return parsed.vue != null
        },
        options: {
          cacheDirectory: vueLoaderUse.options.cacheDirectory,
          cacheIdentifier: vueLoaderUse.options.cacheIdentifier
        }
      }
  
      // replace original rules
      compiler.options.module.rules = [
        pitcher,
        ...clonedRules,
        ...rules
      ]
    }
  }
  
  function createMatcher (fakeFile) {
    return (rule, i) => {
      // #1201 we need to skip the `include` check when locating the vue rule
      const clone = Object.assign({}, rule)
      delete clone.include
      const normalized = RuleSet.normalizeRule(clone, {}, '')
      return (
        !rule.enforce &&
        normalized.resource &&
        normalized.resource(fakeFile)
      )
    }
  }
  
  function cloneRule (rule) {
    const { resource, resourceQuery } = rule
    // Assuming `test` and `resourceQuery` tests are executed in series and
    // synchronously (which is true based on RuleSet's implementation), we can
    // save the current resource being matched from `test` so that we can access
    // it in `resourceQuery`. This ensures when we use the normalized rule's
    // resource check, include/exclude are matched correctly.
    let currentResource
    const res = Object.assign({}, rule, {
      resource: {
        test: resource => {
          currentResource = resource
          return true
        }
      },
      resourceQuery: query => {
        const parsed = qs.parse(query.slice(1))
        if (parsed.vue == null) {
          return false
        }
        if (resource && parsed.lang == null) {
          return false
        }
        const fakeResourcePath = `${currentResource}.${parsed.lang}`
        if (resource && !resource(fakeResourcePath)) {
          return false
        }
        if (resourceQuery && !resourceQuery(query)) {
          return false
        }
        return true
      }
    })
  
    if (rule.oneOf) {
      res.oneOf = rule.oneOf.map(cloneRule)
    }
  
    return res
  }
  
  VueLoaderPlugin.NS = NS
  module.exports = VueLoaderPlugin
  