Commit b2f3ef9c authored by Evan You's avatar Evan You

refactor: simplify plugin implementation

This also removes the need for ident reuse whitelist
and properly shares ident between original and cloned
rules.
parent 698a79db
...@@ -25,29 +25,16 @@ class VueLoaderPlugin { ...@@ -25,29 +25,16 @@ class VueLoaderPlugin {
}) })
} }
// get a hold of the raw rules
const rawRules = compiler.options.module.rules.slice()
// use webpack's RuleSet utility to normalize user rules // use webpack's RuleSet utility to normalize user rules
const rawNormalizedRules = new RuleSet(rawRules).rules const rawRules = compiler.options.module.rules
const { rules } = new RuleSet(rawRules)
const createMatcher = fakeFile => (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)
)
}
// find the rule that applies to vue files // find the rule that applies to vue files
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`)) let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
if (vueRuleIndex < 0) { if (vueRuleIndex < 0) {
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`)) vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
} }
const vueRule = rawRules[vueRuleIndex] const vueRule = rules[vueRuleIndex]
if (!vueRule) { if (!vueRule) {
throw new Error( throw new Error(
...@@ -62,12 +49,10 @@ class VueLoaderPlugin { ...@@ -62,12 +49,10 @@ class VueLoaderPlugin {
) )
} }
// find the normalized version of the vue rule
const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
// get the normlized "use" for vue files // get the normlized "use" for vue files
const normalizedVueUse = normalizedVueRule.use const vueUse = vueRule.use
// get vue-loader options // get vue-loader options
const vueLoaderUseIndex = normalizedVueUse.findIndex(u => { const vueLoaderUseIndex = vueUse.findIndex(u => {
return /^vue-loader|(\/|\\)vue-loader/.test(u.loader) return /^vue-loader|(\/|\\)vue-loader/.test(u.loader)
}) })
...@@ -81,59 +66,51 @@ class VueLoaderPlugin { ...@@ -81,59 +66,51 @@ class VueLoaderPlugin {
// make sure vue-loader options has a known ident so that we can share // 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 // options by reference in the template-loader by using a ref query like
// template-loader??vue-loader-options // template-loader??vue-loader-options
const ident = 'vue-loader-options' const vueLoaderUse = vueUse[vueLoaderUseIndex]
const vueLoaderUse = normalizedVueUse[vueLoaderUseIndex] vueLoaderUse.ident = 'vue-loader-options'
// has options, just set ident vueLoaderUse.options = vueLoaderUse.options || {}
if (vueLoaderUse.options) {
vueLoaderUse.options.ident = ident // for each user rule (expect the vue rule), create a cloned rule
} else { // that targets the corresponding language blocks in *.vue files.
// user provided no options, but we must ensure the options is present const clonedRules = rules
// otherwise RuleSet throws error if no option for a given ref is found. .filter(r => r !== vueRule)
if (vueRule.loader || vueRule.loaders) { .map(cloneRule)
vueRule.options = { ident }
} else if (Array.isArray(vueRule.use)) { // global pitcher (responsible for injecting template compiler loader & CSS
const use = vueRule.use[vueLoaderUseIndex] // post loader)
if (typeof use === 'string') { const pitcher = {
vueRule.use[vueLoaderUseIndex] = { loader: use, options: { ident }}
} else {
use.options = { ident }
}
} else if (typeof vueRule.use === 'string') {
vueRule.use = [{ loader: vueRule.use, options: { ident }}]
} else {
throw new Error(
`VueLoaderPlugin Error: this should not happen. Please open an issue ` +
`with your webpack config.`
)
}
}
// get new rules without the vue rule
const baseRules = rawRules.filter(r => r !== vueRule)
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)
// for each user rule, inject a cloned rule by checking if the rule
// matches the lang specified in the resourceQuery.
rawRules.unshift.apply(rawRules, baseRules.map((rule, i) => {
return cloneRule(rule, normalizedRules[i])
}))
// inject global pitcher (responsible for injecting template compiler
// loader & CSS post loader)
rawRules.unshift({
loader: require.resolve('./loaders/pitcher'), loader: require.resolve('./loaders/pitcher'),
resourceQuery: query => { resourceQuery: query => {
const parsed = qs.parse(query.slice(1)) const parsed = qs.parse(query.slice(1))
return parsed.vue != null return parsed.vue != null
} }
}) }
// replace original rules // replace original rules
compiler.options.module.rules = rawRules compiler.options.module.rules = [
pitcher,
...clonedRules,
...rules
]
} }
} }
function cloneRule (rule, normalizedRule) { 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 // Assuming `test` and `resourceQuery` tests are executed in series and
// synchronously (which is true based on RuleSet's implementation), we can // synchronously (which is true based on RuleSet's implementation), we can
// save the current resource being matched from `test` so that we can access // save the current resource being matched from `test` so that we can access
...@@ -141,16 +118,17 @@ function cloneRule (rule, normalizedRule) { ...@@ -141,16 +118,17 @@ function cloneRule (rule, normalizedRule) {
// resource check, include/exclude are matched correctly. // resource check, include/exclude are matched correctly.
let currentResource let currentResource
const res = Object.assign({}, rule, { const res = Object.assign({}, rule, {
resource: {
test: resource => { test: resource => {
currentResource = resource currentResource = resource
return true return true
}
}, },
resourceQuery: query => { resourceQuery: query => {
const parsed = qs.parse(query.slice(1)) const parsed = qs.parse(query.slice(1))
if (parsed.vue == null) { if (parsed.vue == null) {
return false return false
} }
const { resource, resourceQuery } = normalizedRule
if (resource && parsed.lang == null) { if (resource && parsed.lang == null) {
return false return false
} }
...@@ -162,50 +140,15 @@ function cloneRule (rule, normalizedRule) { ...@@ -162,50 +140,15 @@ function cloneRule (rule, normalizedRule) {
return false return false
} }
return true return true
}, }
use: normalizedRule.use ? normalizedRule.use.map(cleanIdent) : undefined
}) })
// delete shorthand since we have normalized use
delete res.loader
delete res.loaders
delete res.options
// these are included in the normalized resource() check
delete res.resource
delete res.include
delete res.exclude
if (rule.oneOf) { if (rule.oneOf) {
res.oneOf = rule.oneOf.map((r, i) => { res.oneOf = rule.oneOf.map(cloneRule)
return cloneRule(r, normalizedRule.oneOf[i])
})
} }
return res return res
} }
const reuseIdentWhitelist = [
'css-loader',
'(vue-)?style-loader',
'postcss-loader',
'extract-text-webpack-plugin',
'mini-css-extract-plugin'
]
const reuseIdentPattern = new RegExp(`(${reuseIdentWhitelist.join('|')})`)
function cleanIdent (use) {
if (use.ident) {
if (reuseIdentPattern.test(use.loader)) {
// Reuse options ident, so that imports from within css-loader would get the
// exact same request prefixes, avoiding duplicated modules (#1199)
use.options.ident = use.ident
}
delete use.ident
}
return use
}
VueLoaderPlugin.NS = NS VueLoaderPlugin.NS = NS
module.exports = VueLoaderPlugin module.exports = VueLoaderPlugin
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment