| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 | const qs = require('querystring')const RuleSet = require('webpack/lib/RuleSet')const { resolveCompiler } = require('./compiler')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) => {        const normalModuleLoader = compilation.hooks.normalModuleLoader        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 normalized "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 (except 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)    // rule for template compiler    const templateCompilerRule = {      loader: require.resolve('./loaders/templateLoader'),      resourceQuery: (query) => {        const parsed = qs.parse(query.slice(1))        return parsed.vue != null && parsed.type === 'template'      },      options: vueLoaderUse.options    }    // for each rule that matches plain .js/.ts files, also create a clone and    // match it against the compiled template code inside *.vue files, so that    // compiled vue render functions receive the same treatment as user code    // (mostly babel)    const { is27 } = resolveCompiler(compiler.options.context)    let jsRulesForRenderFn = []    if (is27) {      const matchesJS = createMatcher(`test.js`)      // const matchesTS = createMatcher(`test.ts`)      jsRulesForRenderFn = rules        .filter((r) => r !== vueRule && matchesJS(r))        .map(cloneRuleForRenderFn)    }    // 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,      ...jsRulesForRenderFn,      ...(is27 ? [templateCompilerRule] : []),      ...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.rules) {    res.rules = rule.rules.map(cloneRule)  }  if (rule.oneOf) {    res.oneOf = rule.oneOf.map(cloneRule)  }  return res}function cloneRuleForRenderFn(rule) {  const resource = rule.resource  const resourceQuery = rule.resourceQuery  let currentResource  const res = {    ...rule,    resource: (resource) => {      currentResource = resource      return true    },    resourceQuery: (query) => {      const parsed = qs.parse(query.slice(1))      if (parsed.vue == null || parsed.type !== 'template') {        return false      }      const fakeResourcePath = `${currentResource}.${parsed.ts ? `ts` : `js`}`      if (resource && !resource(fakeResourcePath)) {        return false      }      if (resourceQuery && !resourceQuery(query)) {        return false      }      return true    }  }  // Filter out `thread-loader` from the `use` array.  // Mitigate https://github.com/vuejs/vue/issues/12828  // Note this won't work if the `use` filed is a function  if (Array.isArray(res.use)) {    const isThreadLoader = (loader) => loader === 'thread-loader' || /\/node_modules\/thread-loader\//.test(loader)    res.use = res.use.filter(useEntry => {      const loader = typeof useEntry === 'string' ? useEntry : useEntry.loader      return !isThreadLoader(loader)    })  }  if (rule.rules) {    res.rules = rule.rules.map(cloneRuleForRenderFn)  }  if (rule.oneOf) {    res.oneOf = rule.oneOf.map(cloneRuleForRenderFn)  }  return res}VueLoaderPlugin.NS = NSmodule.exports = VueLoaderPlugin
 |