Skip to content

Commit b2f3ef9

Browse files
committed
refactor: simplify plugin implementation
This also removes the need for ident reuse whitelist and properly shares ident between original and cloned rules.
1 parent 698a79d commit b2f3ef9

File tree

1 file changed

+47
-104
lines changed

1 file changed

+47
-104
lines changed

Diff for: lib/plugin.js

+47-104
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,16 @@ class VueLoaderPlugin {
2525
})
2626
}
2727

28-
// get a hold of the raw rules
29-
const rawRules = compiler.options.module.rules.slice()
3028
// use webpack's RuleSet utility to normalize user rules
31-
const rawNormalizedRules = new RuleSet(rawRules).rules
32-
33-
const createMatcher = fakeFile => (rule, i) => {
34-
// #1201 we need to skip the `include` check when locating the vue rule
35-
const clone = Object.assign({}, rule)
36-
delete clone.include
37-
const normalized = RuleSet.normalizeRule(clone, {}, '')
38-
return (
39-
!rule.enforce &&
40-
normalized.resource &&
41-
normalized.resource(fakeFile)
42-
)
43-
}
29+
const rawRules = compiler.options.module.rules
30+
const { rules } = new RuleSet(rawRules)
4431

4532
// find the rule that applies to vue files
4633
let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
4734
if (vueRuleIndex < 0) {
4835
vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
4936
}
50-
const vueRule = rawRules[vueRuleIndex]
37+
const vueRule = rules[vueRuleIndex]
5138

5239
if (!vueRule) {
5340
throw new Error(
@@ -62,12 +49,10 @@ class VueLoaderPlugin {
6249
)
6350
}
6451

65-
// find the normalized version of the vue rule
66-
const normalizedVueRule = rawNormalizedRules[vueRuleIndex]
6752
// get the normlized "use" for vue files
68-
const normalizedVueUse = normalizedVueRule.use
53+
const vueUse = vueRule.use
6954
// get vue-loader options
70-
const vueLoaderUseIndex = normalizedVueUse.findIndex(u => {
55+
const vueLoaderUseIndex = vueUse.findIndex(u => {
7156
return /^vue-loader|(\/|\\)vue-loader/.test(u.loader)
7257
})
7358

@@ -81,76 +66,69 @@ class VueLoaderPlugin {
8166
// make sure vue-loader options has a known ident so that we can share
8267
// options by reference in the template-loader by using a ref query like
8368
// template-loader??vue-loader-options
84-
const ident = 'vue-loader-options'
85-
const vueLoaderUse = normalizedVueUse[vueLoaderUseIndex]
86-
// has options, just set ident
87-
if (vueLoaderUse.options) {
88-
vueLoaderUse.options.ident = ident
89-
} else {
90-
// user provided no options, but we must ensure the options is present
91-
// otherwise RuleSet throws error if no option for a given ref is found.
92-
if (vueRule.loader || vueRule.loaders) {
93-
vueRule.options = { ident }
94-
} else if (Array.isArray(vueRule.use)) {
95-
const use = vueRule.use[vueLoaderUseIndex]
96-
if (typeof use === 'string') {
97-
vueRule.use[vueLoaderUseIndex] = { loader: use, options: { ident }}
98-
} else {
99-
use.options = { ident }
100-
}
101-
} else if (typeof vueRule.use === 'string') {
102-
vueRule.use = [{ loader: vueRule.use, options: { ident }}]
103-
} else {
104-
throw new Error(
105-
`VueLoaderPlugin Error: this should not happen. Please open an issue ` +
106-
`with your webpack config.`
107-
)
108-
}
109-
}
110-
111-
// get new rules without the vue rule
112-
const baseRules = rawRules.filter(r => r !== vueRule)
113-
const normalizedRules = rawNormalizedRules.filter(r => r !== normalizedVueRule)
114-
115-
// for each user rule, inject a cloned rule by checking if the rule
116-
// matches the lang specified in the resourceQuery.
117-
rawRules.unshift.apply(rawRules, baseRules.map((rule, i) => {
118-
return cloneRule(rule, normalizedRules[i])
119-
}))
120-
121-
// inject global pitcher (responsible for injecting template compiler
122-
// loader & CSS post loader)
123-
rawRules.unshift({
69+
const vueLoaderUse = vueUse[vueLoaderUseIndex]
70+
vueLoaderUse.ident = 'vue-loader-options'
71+
vueLoaderUse.options = vueLoaderUse.options || {}
72+
73+
// for each user rule (expect the vue rule), create a cloned rule
74+
// that targets the corresponding language blocks in *.vue files.
75+
const clonedRules = rules
76+
.filter(r => r !== vueRule)
77+
.map(cloneRule)
78+
79+
// global pitcher (responsible for injecting template compiler loader & CSS
80+
// post loader)
81+
const pitcher = {
12482
loader: require.resolve('./loaders/pitcher'),
12583
resourceQuery: query => {
12684
const parsed = qs.parse(query.slice(1))
12785
return parsed.vue != null
12886
}
129-
})
87+
}
13088

13189
// replace original rules
132-
compiler.options.module.rules = rawRules
90+
compiler.options.module.rules = [
91+
pitcher,
92+
...clonedRules,
93+
...rules
94+
]
95+
}
96+
}
97+
98+
function createMatcher (fakeFile) {
99+
return (rule, i) => {
100+
// #1201 we need to skip the `include` check when locating the vue rule
101+
const clone = Object.assign({}, rule)
102+
delete clone.include
103+
const normalized = RuleSet.normalizeRule(clone, {}, '')
104+
return (
105+
!rule.enforce &&
106+
normalized.resource &&
107+
normalized.resource(fakeFile)
108+
)
133109
}
134110
}
135111

136-
function cloneRule (rule, normalizedRule) {
112+
function cloneRule (rule) {
113+
const { resource, resourceQuery } = rule
137114
// Assuming `test` and `resourceQuery` tests are executed in series and
138115
// synchronously (which is true based on RuleSet's implementation), we can
139116
// save the current resource being matched from `test` so that we can access
140117
// it in `resourceQuery`. This ensures when we use the normalized rule's
141118
// resource check, include/exclude are matched correctly.
142119
let currentResource
143120
const res = Object.assign({}, rule, {
144-
test: resource => {
145-
currentResource = resource
146-
return true
121+
resource: {
122+
test: resource => {
123+
currentResource = resource
124+
return true
125+
}
147126
},
148127
resourceQuery: query => {
149128
const parsed = qs.parse(query.slice(1))
150129
if (parsed.vue == null) {
151130
return false
152131
}
153-
const { resource, resourceQuery } = normalizedRule
154132
if (resource && parsed.lang == null) {
155133
return false
156134
}
@@ -162,50 +140,15 @@ function cloneRule (rule, normalizedRule) {
162140
return false
163141
}
164142
return true
165-
},
166-
use: normalizedRule.use ? normalizedRule.use.map(cleanIdent) : undefined
143+
}
167144
})
168145

169-
// delete shorthand since we have normalized use
170-
delete res.loader
171-
delete res.loaders
172-
delete res.options
173-
174-
// these are included in the normalized resource() check
175-
delete res.resource
176-
delete res.include
177-
delete res.exclude
178-
179146
if (rule.oneOf) {
180-
res.oneOf = rule.oneOf.map((r, i) => {
181-
return cloneRule(r, normalizedRule.oneOf[i])
182-
})
147+
res.oneOf = rule.oneOf.map(cloneRule)
183148
}
184149

185150
return res
186151
}
187152

188-
const reuseIdentWhitelist = [
189-
'css-loader',
190-
'(vue-)?style-loader',
191-
'postcss-loader',
192-
'extract-text-webpack-plugin',
193-
'mini-css-extract-plugin'
194-
]
195-
196-
const reuseIdentPattern = new RegExp(`(${reuseIdentWhitelist.join('|')})`)
197-
198-
function cleanIdent (use) {
199-
if (use.ident) {
200-
if (reuseIdentPattern.test(use.loader)) {
201-
// Reuse options ident, so that imports from within css-loader would get the
202-
// exact same request prefixes, avoiding duplicated modules (#1199)
203-
use.options.ident = use.ident
204-
}
205-
delete use.ident
206-
}
207-
return use
208-
}
209-
210153
VueLoaderPlugin.NS = NS
211154
module.exports = VueLoaderPlugin

0 commit comments

Comments
 (0)